diff --git a/CHANGELOG b/CHANGELOG index 517643267a..cda4b87303 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ === master +* Support :adder, :remover, and :clearer association options that use keyword arguments in Ruby 2.7+ (jeremyevans) + * Make pg_interval use the same number of seconds per year and per month as ActiveSupport::Duration when using ActiveSupport 5.1+ (jeremyevans) === 5.40.0 (2021-01-01) diff --git a/lib/sequel/model/associations.rb b/lib/sequel/model/associations.rb index 9aa591b0d9..10edd994d3 100644 --- a/lib/sequel/model/associations.rb +++ b/lib/sequel/model/associations.rb @@ -1929,8 +1929,22 @@ def association_module(opts=OPTS) # can be easily overridden in the class itself while allowing for # super to be called. def association_module_def(name, opts=OPTS, &block) - association_module(opts).send(:define_method, name, &block) - association_module(opts).send(:alias_method, name, name) + mod = association_module(opts) + mod.send(:define_method, name, &block) + mod.send(:alias_method, name, name) + end + + # Add a method to the module included in the class, so the method + # can be easily overridden in the class itself while allowing for + # super to be called. This method allows passing keywords through + # the defined methods. + def association_module_delegate_def(name, opts, &block) + mod = association_module(opts) + mod.send(:define_method, name, &block) + # :nocov: + mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true) + # :nocov: + mod.send(:alias_method, name, name) end # Add a private method to the module included in the class. @@ -1982,17 +1996,17 @@ def def_association_instance_methods(opts) if adder = opts[:adder] association_module_private_def(opts[:_add_method], opts, &adder) - association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)} + association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)} end if remover = opts[:remover] association_module_private_def(opts[:_remove_method], opts, &remover) - association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)} + association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)} end if clearer = opts[:clearer] association_module_private_def(opts[:_remove_all_method], opts, &clearer) - association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)} + association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)} end end @@ -2424,6 +2438,9 @@ def add_associated_object(opts, o, *args) run_association_callbacks(opts, :after_add, o) o end + # :nocov: + ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true) + # :nocov: # Add/Set the current object to/as the given object's reciprocal association. def add_reciprocal_object(opts, o) @@ -2566,6 +2583,9 @@ def remove_all_associated_objects(opts, *args) associations[opts[:name]] = [] ret end + # :nocov: + ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true) + # :nocov: # Remove the given associated object from the given association def remove_associated_object(opts, o, *args) @@ -2587,6 +2607,9 @@ def remove_associated_object(opts, o, *args) run_association_callbacks(opts, :after_remove, o) o end + # :nocov: + ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true) + # :nocov: # Check that the object from the associated table specified by the primary key # is currently associated to the receiver. If it is associated, return the object, otherwise diff --git a/spec/model/associations_spec.rb b/spec/model/associations_spec.rb index b9703d4206..3d1607bad3 100644 --- a/spec/model/associations_spec.rb +++ b/spec/model/associations_spec.rb @@ -2011,6 +2011,15 @@ def c._node_id=; raise; end p.instance_variable_get(:@x).must_equal c end + it "should support an :adder option that accepts keywords" do + @c2.one_to_many :attributes, :class => @c1, :adder=>eval('proc{|x, foo: nil| @x = [x, foo]}') + p = @c2.load(:id=>10) + c = @c1.load(:id=>123) + def c._node_id=; raise; end + p.add_attribute(c, foo: 1) + p.instance_variable_get(:@x).must_equal [c, 1] + end if RUBY_VERSION >= '2.0' + it "should allow additional arguments given to the add_ method and pass them onwards to the _add_ method" do @c2.one_to_many :attributes, :class => @c1 p = @c2.load(:id=>10) @@ -2047,6 +2056,15 @@ def c._node_id=; raise; end p.instance_variable_get(:@x).must_equal c end + it "should support a :remover option that accepts keywords" do + @c2.one_to_many :attributes, :class => @c1, :remover=>eval('proc{|x, foo: nil| @x = [x, foo]}') + p = @c2.load(:id=>10) + c = @c1.load(:id=>123) + def c._node_id=; raise; end + p.remove_attribute(c, foo: 1) + p.instance_variable_get(:@x).must_equal [c, 1] + end if RUBY_VERSION >= '2.0' + it "should allow additional arguments given to the remove_ method and pass them onwards to the _remove_ method" do @c2.one_to_many :attributes, :class => @c1, :reciprocal=>nil p = @c2.load(:id=>10) @@ -2091,6 +2109,13 @@ def p._remove_all_attributes p.instance_variable_get(:@x).must_equal :foo end + it "should support a :clearer option that supports keywords" do + @c2.one_to_many :attributes, :class => @c1, :clearer=>eval('proc{|foo: nil| @x = foo}') + p = @c2.load(:id=>10) + p.remove_all_attributes(foo: 1) + p.instance_variable_get(:@x).must_equal 1 + end if RUBY_VERSION >= '2.0' + it "should support (before|after)_(add|remove) callbacks" do h = [] @c2.one_to_many :attributes, :class => @c1, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]