lib/neo4j/active_node/has_n.rb in neo4j-5.0.15 vs lib/neo4j/active_node/has_n.rb in neo4j-5.1.0.rc.1

- old
+ new

@@ -22,11 +22,11 @@ # Default def inspect if @cached_result @cached_result.inspect else - "<AssociationProxy @query_proxy=#{@query_proxy.inspect}>" + "#<AssociationProxy @query_proxy=#{@query_proxy.inspect}>" end end extend Forwardable %w(include? empty? count find first last ==).each do |delegated_method| @@ -119,11 +119,11 @@ self.class.send(:association_query_proxy, name, {start_object: self}.merge!(options)) end def association_proxy(name, options = {}) name = name.to_sym - hash = [name, options.values_at(:node, :rel, :labels)].hash + hash = [name, options.values_at(:node, :rel, :labels, :rel_length)].hash association_proxy_cache_fetch(hash) do if result_cache = self.instance_variable_get('@source_query_proxy_result_cache') result_by_previous_id = previous_proxy_results_by_previous_id(result_cache, name) result_cache.inject(nil) do |proxy_to_return, object| @@ -150,31 +150,23 @@ query_proxy = self.class.send(:association_query_proxy, association_name, previous_query_proxy: query_proxy, node: :next, optional: true) Hash[*query_proxy.pluck('ID(previous)', 'collect(next)').flatten(1)] end - def handle_non_persisted_node(other_node) - return unless Neo4j::Config[:autosave_on_assignment] - other_node.try(:save) - save - end - - def validate_persisted_for_association! - fail(Neo4j::ActiveNode::HasN::NonPersistedNodeError, 'Unable to create relationship with non-persisted nodes') unless self._persisted_obj - end - module ClassMethods - # :nocov: # rubocop:disable Style/PredicateName + + # :nocov: def has_association?(name) ActiveSupport::Deprecation.warn 'has_association? is deprecated and may be removed from future releases, use association? instead.', caller association?(name) end - # rubocop:enable Style/PredicateName # :nocov: + # rubocop:enable Style/PredicateName + def association?(name) !!associations[name.to_sym] end def associations @@ -193,12 +185,15 @@ end # For defining an "has many" association on a model. This defines a set of methods on # your model instances. For instance, if you define the association on a Person model: # - # has_many :out, :vehicles, type: :has_vehicle # + # .. code-block:: ruby + # + # has_many :out, :vehicles, type: :has_vehicle + # # This would define the following methods: # # **#vehicles** # Returns a QueryProxy object. This is an Enumerable object and thus can be iterated # over. It also has the ability to accept class-level methods from the Vehicle model @@ -211,51 +206,64 @@ # **.vehicles** # Returns a QueryProxy object. This would represent all ``Vehicle`` objects associated with # either all ``Person`` nodes (if ``Person.vehicles`` is called), or all ``Vehicle`` objects # associated with the ``Person`` nodes thus far represented in the QueryProxy chain. # For example: - # ``company.people.where(age: 40).vehicles`` # + # .. code-block:: ruby + # + # company.people.where(age: 40).vehicles + # # Arguments: # **direction:** # **Available values:** ``:in``, ``:out``, or ``:both``. # # Refers to the relative to the model on which the association is being defined. # # Example: - # ``Person.has_many :out, :posts, type: :wrote`` # - # means that a `WROTE` relationship goes from a `Person` node to a `Post` node + # .. code-block:: ruby # + # Person.has_many :out, :posts, type: :wrote + # + # means that a `WROTE` relationship goes from a `Person` node to a `Post` node + # # **name:** # The name of the association. The affects the methods which are created (see above). # The name is also used to form default assumptions about the model which is being referred to # # Example: - # ``Person.has_many :out, :posts, type: :wrote`` # - # will assume a `model_class` option of ``'Post'`` unless otherwise specified + # .. code-block:: ruby # + # Person.has_many :out, :posts, type: :wrote + # + # will assume a `model_class` option of ``'Post'`` unless otherwise specified + # # **options:** A ``Hash`` of options. Allowed keys are: # *type*: The Neo4j relationship type. This option is required unless either the # `origin` or `rel_class` options are specified # # *origin*: The name of the association from another model which the `type` and `model_class` # can be gathered. # # Example: - # ``Person.has_many :out, :posts, origin: :author`` (`model_class` of `Post` is assumed here) # - # ``Post.has_one :in, :author, type: :has_author, model_class: 'Person'`` + # .. code-block:: ruby # + # # `model_class` of `Post` is assumed here + # Person.has_many :out, :posts, origin: :author + # + # Post.has_one :in, :author, type: :has_author, model_class: 'Person' + # # *model_class*: The model class to which the association is referring. Can be either a - # model object ``include`` ing ``ActiveNode`` or a string (or an ``Array`` of same). - # **A string is recommended** to avoid load-time issues + # model object ``include`` ing ``ActiveNode`` or a Symbol/String (or an ``Array`` of same). + # **A Symbol or String is recommended** to avoid load-time issues # # *rel_class*: The ``ActiveRel`` class to use for this association. Can be either a - # model object ``include`` ing ``ActiveRel`` or a string (or an ``Array`` of same). - # **A string is recommended** to avoid load-time issues + # model object ``include`` ing ``ActiveRel`` or a Symbol/String (or an ``Array`` of same). + # **A Symbol or String is recommended** to avoid load-time issues # # *dependent*: Enables deletion cascading. # **Available values:** ``:delete``, ``:delete_orphans``, ``:destroy``, ``:destroy_orphans`` # (note that the ``:destroy_orphans`` option is known to be "very metal". Caution advised) # @@ -285,17 +293,24 @@ private def define_has_many_methods(name) define_method(name) do |node = nil, rel = nil, options = {}| - return [].freeze unless self._persisted_obj + # return [].freeze unless self._persisted_obj + if node.is_a?(Hash) + options = node + node = nil + end + association_proxy(name, {node: node, rel: rel, source_object: self, labels: options[:labels]}.merge!(options)) end define_has_many_setter(name) + define_has_many_id_methods(name) + define_class_method(name) do |node = nil, rel = nil, options = {}| association_proxy(name, {node: node, rel: rel, labels: options[:labels]}.merge!(options)) end end @@ -305,30 +320,82 @@ Neo4j::Transaction.run { association_proxy(name).replace_with(other_nodes) } end end - def define_has_one_methods(name) - define_method(name) do |node = nil, rel = nil| - return nil unless self._persisted_obj + def define_has_many_id_methods(name) + define_method_unless_defined("#{name.to_s.singularize}_ids") do + association_proxy(name).pluck(:uuid) + end - association_proxy(name, node: node, rel: rel).first + define_method_unless_defined("#{name.to_s.singularize}_ids=") do |ids| + association_proxy(name).replace_with(ids) end + define_method_unless_defined("#{name.to_s.singularize}_neo_ids") do + association_proxy(name).pluck(:neo_id) + end + end + + def define_method_unless_defined(method_name, &block) + define_method(method_name, block) unless respond_to?(method_name) + end + + def define_has_one_methods(name) + define_has_one_getter(name) + define_has_one_setter(name) + define_has_one_id_methods(name) + define_class_method(name) do |node = nil, rel = nil, options = {}| association_proxy(name, {node: node, rel: rel, labels: options[:labels]}.merge!(options)) end end + def define_has_one_id_methods(name) + define_method("#{name}_id") do + association_proxy(name).pluck(:uuid).first + end + + define_method_unless_defined("#{name}_id=") do |id| + association_proxy(name).replace_with(id) + end + + define_method("#{name}_neo_id") do + association_proxy(name).pluck(:neo_id).first + end + end + + def define_has_one_getter(name) + define_method(name) do |node = nil, rel = nil, options = {}| + return nil unless self._persisted_obj + + if node.is_a?(Hash) + options = node + node = nil + end + + # Return all results if a variable-length relationship length was given + results = association_proxy(name, {node: node, rel: rel}.merge!(options)) + if options[:rel_length] && !options[:rel_length].is_a?(Fixnum) + results + else + results.first + end + end + end + def define_has_one_setter(name) define_method("#{name}=") do |other_node| - handle_non_persisted_node(other_node) - validate_persisted_for_association! - association_proxy_cache.clear # TODO: Should probably just clear for this association... - - Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) } + if persisted? + other_node.save if other_node.respond_to?(:persisted?) && !other_node.persisted? + association_proxy_cache.clear # TODO: Should probably just clear for this association... + Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) } + # handle_non_persisted_node(other_node) + else + association_proxy(name).defer_create(other_node, {}, :'=') + end end end def define_class_method(*args, &block) klass = class << self; self; end