Skip to content
Browse files

Allow the :dataset association option to accept the association refle…

…ction as an argument

This fixes the limitation of the previous commit, where custom
:dataset options could not benefit from the caching of intermediate
association datasets.  With this change, the :dataset proc can
accept an option, in which case it is passed the association
reflection, on which the associated_dataset method can be called.

This change has some minor backwards incompatibility.  The private
_association_dataset method (note preceding underscore) is no longer
defined, and the public AssociationReflection#_dataset_method method
is also no longer defined.
  • Loading branch information...
1 parent 22a17b6 commit 1f65d1696ed038998091e2bef7f5fca2314f74da @jeremyevans committed Jan 9, 2013
Showing with 34 additions and 12 deletions.
  1. +2 −0 CHANGELOG
  2. +10 −3 doc/association_basics.rdoc
  3. +9 −9 lib/sequel/model/associations.rb
  4. +13 −0 spec/model/associations_spec.rb
View
2 CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD
+* Allow the :dataset association option to accept the association reflection as an argument (jeremyevans)
+
* Improve association method performance by caching intermediate dataset (jeremyevans)
=== 3.43.0 (2013-01-08)
View
13 doc/association_basics.rdoc
@@ -1007,14 +1007,21 @@ in additon to the :clone option.
==== :dataset
This is generally only specified for custom associations that aren't based on
-primary/foreign key relationships. It should be a proc that is instance evaled
+primary/foreign key relationships. It should be a proc that is instance_execed
to get the base dataset to use before the other options are applied.
+If the proc accepts an argument, it is passed the related association reflection.
+For best performance, it's recommended that custom associations call the
++associated_dataset+ method on the association reflection as the starting point
+for the dataset to return. The +associated_dataset+ method will return a
+dataset based on the associated class with most of the association options
+already applied, and the proc should return a modified copy of this dataset.
+
Here's an example of an association of songs to artists through lyrics, where
the artist can perform any one of four tasks for the lyric:
- Album.one_to_many :songs, :dataset=>(proc do
- Song.select_all(:songs).
+ Album.one_to_many :songs, :dataset=>(proc do |r|
+ r.associated_dataset.select_all(:songs).
join(Lyric, :id=>:lyricid,
id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])
end)
View
18 lib/sequel/model/associations.rb
@@ -22,11 +22,6 @@ def _add_method
:"_add_#{singularize(self[:name])}"
end
- # Name symbol for the _dataset association method
- def _dataset_method
- :"_#{self[:name]}_dataset"
- end
-
# Name symbol for the _remove_all internal association method
def _remove_all_method
:"_remove_all_#{self[:name]}"
@@ -771,8 +766,9 @@ def apply_association_dataset_opts(opts, ds)
# the given association. Can be used to DRY up a bunch of similar associations that
# all share the same options such as :class and :key, while changing the order and block used.
# :conditions :: The conditions to use to filter the association, can be any argument passed to where.
- # :dataset :: A proc that is instance_evaled to get the base dataset
- # to use for the _dataset method (before the other options are applied).
+ # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
+ # options are applied). If the proc accepts an argument, it is passed the related
+ # association reflection.
# :distinct :: Use the DISTINCT clause when selecting associating object, both when
# lazy loading and eager loading via .eager (but not when using .eager_graph).
# :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1114,7 +1110,6 @@ def def_add_method(opts)
# Adds the association dataset methods to the association methods module.
def def_association_dataset_methods(opts)
- association_module_private_def(opts._dataset_method, opts, &opts[:dataset])
association_module_def(opts.dataset_method, opts){_dataset(opts)}
def_association_method(opts)
end
@@ -1473,7 +1468,12 @@ def _associated_dataset(opts, dynamic_opts)
# Return an association dataset for the given association reflection
def _dataset(opts)
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
- _apply_association_options(opts, send(opts._dataset_method))
+ ds = if opts[:dataset].arity == 1
+ instance_exec(opts, &opts[:dataset])
+ else
+ instance_exec(&opts[:dataset])
+ end
+ _apply_association_options(opts, ds)
end
# Dataset for the join table of the given many to many association reflection
View
13 spec/model/associations_spec.rb
@@ -1924,6 +1924,19 @@ class Tag < Sequel::Model; end
MODEL_DB.sqls.should == ['SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10']
end
+ it "should support a :dataset option that accepts the reflection as an argument" do
+ c1 = @c1
+ @c2.many_to_many :attributes, :class => @c1, :dataset=>lambda{|opts| opts.associated_dataset.join_table(:natural, :an).filter(:an__nodeid=>pk)}, :order=> :a, :limit=>10, :select=>nil do |ds|
+ ds.filter(:xxx => @xxx)
+ end
+
+ n = @c2.new(:id => 1234)
+ n.xxx = 555
+ n.attributes_dataset.sql.should == 'SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10'
+ n.attributes.should == [@c1.load({})]
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes NATURAL JOIN an WHERE ((an.nodeid = 1234) AND (xxx = 555)) ORDER BY a LIMIT 10']
+ end
+
it "should support a :limit option" do
@c2.many_to_many :attributes, :class => @c1 , :limit=>10
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) LIMIT 10'

0 comments on commit 1f65d16

Please sign in to comment.
Something went wrong with that request. Please try again.