diff --git a/sequel/CHANGELOG b/sequel/CHANGELOG index 2b333a8986..ace877e378 100644 --- a/sequel/CHANGELOG +++ b/sequel/CHANGELOG @@ -1,5 +1,9 @@ === HEAD +* Add :extend association option, extending the dataset with module(s) (jeremyevans) + +* Add :after_load association callback option, called after associated objects have been loaded from the database (jeremyevans) + * Make validation methods support a :tag option, to work correctly with source reloading (jeremyevans) * Add :before_add, :after_add, :before_remove, :after_remove association callback options (jeremyevans) diff --git a/sequel/doc/advanced_associations.rdoc b/sequel/doc/advanced_associations.rdoc index d27f97195a..eda42be048 100644 --- a/sequel/doc/advanced_associations.rdoc +++ b/sequel/doc/advanced_associations.rdoc @@ -151,9 +151,22 @@ otherwise modified: one_to_many :authorships end Author.first.authorships_dataset.filter(:number < 10).first - -If you want to add methods to the association instead of just using existing -methods: + +You can extend a dataset with a module easily with :extend: + + module FindOrCreate + def find_or_create(vals) + first(vals) || @opts[:models][nil].create(vals) + end + end + class Author < Sequel::Model + one_to_many :authorships, :extend=>FindOrCreate + end + Author.first.authorships_dataset.find_or_create(:name=>'Blah', :number=>10) + +However, note that the dataset doesn't have any knowledge of the model object +that created it via the association, so if you want to use attributes of the +model object, you'll have to use a closure: class Author < Sequel::Model one_to_many :authorships, :dataset=>(proc do @@ -167,6 +180,18 @@ methods: end Author.first.authorships_dataset.find_or_create_by_name('Bob') +You can cheat if you want to: + + module FindOrCreate + def find_or_create(vals) + # Exploits the fact that Sequel filters are ruby objects that + # can be introspected. + author_id = @opts[:where].args[1] + first(vals) || \ + @opts[:models][nil].create(vals.merge(:author_id=>author_id)) + end + end + ===has_many :through associations many_to_many handles the usual case of a has_many :through with a belongs_to in diff --git a/sequel/lib/sequel_model/associations.rb b/sequel/lib/sequel_model/associations.rb index 30350d746d..b7e9966b23 100644 --- a/sequel/lib/sequel_model/associations.rb +++ b/sequel/lib/sequel_model/associations.rb @@ -118,6 +118,7 @@ def all_association_reflections # and a hash of dependent associations. The associated records should # be queried from the database and the associations cache for each # record should be populated for this to work correctly. + # - :extend - A module or array of modules to extend the dataset with. # - :graph_block - The block to pass to join_table when eagerly loading # the association via eager_graph. # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading @@ -193,7 +194,7 @@ def associate(type, name, opts = {}, &block) opts[:graph_join_type] ||= :left_outer opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : [] opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select] - [:before_add, :before_remove, :after_add, :after_remove, :after_load].each do |cb_type| + [:before_add, :before_remove, :after_add, :after_remove, :after_load, :extend].each do |cb_type| opts[cb_type] = Array(opts[cb_type]) end @@ -333,6 +334,7 @@ def def_association_dataset_methods(opts) eager = opts[:eager] eager_graph = opts[:eager_graph] limit = opts[:limit] + exten = opts[:extend] single = opts[:type] == :many_to_one key = opts[:key] if opts[:type] == :many_to_one set_reciprocal = opts[:type] == :one_to_many @@ -348,6 +350,7 @@ def def_association_dataset_methods(opts) class_def(dataset_method) do raise(Sequel::Error, 'model object does not have a primary key') unless pk || single ds = instance_eval(&dataset) + exten.each{|m| ds.extend(m)} ds = ds.select(*opts.select) if opts.select ds = ds.order(*order) if order ds = ds.limit(*limit) if limit diff --git a/sequel/spec/associations_spec.rb b/sequel/spec/associations_spec.rb index 262837cfc5..b3c5f117bf 100644 --- a/sequel/spec/associations_spec.rb +++ b/sequel/spec/associations_spec.rb @@ -15,6 +15,32 @@ class ParParent < Sequel::Model klass.association_reflection(:"par_parent1s").associated_class.should == ParParent klass.association_reflection(:"par_parent2s").associated_class.should == ParParent end + it "should allow extending the dataset with :extend option" do + MODEL_DB.reset + klass = Class.new(Sequel::Model(:nodes)) do + columns :id, :a_id + end + mod = Module.new do + def blah + 1 + end + end + mod2 = Module.new do + def blar + 2 + end + end + + klass.associate :many_to_one, :a, :class=>klass, :extend=>mod + klass.associate :one_to_many, :bs, :class=>klass, :extend=>[mod] + klass.associate :many_to_many, :cs, :class=>klass, :extend=>[mod, mod2] + + node = klass.load(:id=>1) + node.a_dataset.blah.should == 1 + node.bs_dataset.blah.should == 1 + node.cs_dataset.blah.should == 1 + node.cs_dataset.blar.should == 2 + end end describe Sequel::Model, "many_to_one" do