acts-as-joinable 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -96,4 +96,6 @@ The features are:
96
96
 
97
97
  You can always add columns to the relationship table, but the foundation is set.
98
98
 
99
- <cite>copyright @viatropos 2010</cite>
99
+ <cite>copyright @viatropos 2010</cite>
100
+
101
+ http://rors.org/2008/10/26/dont-escape-in-strings
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
5
5
  spec = Gem::Specification.new do |s|
6
6
  s.name = "acts-as-joinable"
7
7
  s.authors = ["Lance Pollard"]
8
- s.version = "0.0.6"
8
+ s.version = "0.1.0"
9
9
  s.summary = "ActsAsJoinable: DRYing up Many-to-Many Relationships in ActiveRecord"
10
10
  s.homepage = "http://github.com/viatropos/acts-as-joinable"
11
11
  s.email = "lancejpollard@gmail.com"
@@ -70,6 +70,9 @@ module ActsAsJoinable
70
70
  def acts_as_relationship
71
71
  belongs_to :parent, :polymorphic => true
72
72
  belongs_to :child, :polymorphic => true
73
+
74
+ # validates_uniqueness_of :parent_id, :scope => [:parent_type, :child_id]
75
+ # validates_uniqueness_of :child_id, :scope => [:child_type, :parent_id]
73
76
 
74
77
  # ActsAsJoinable.models.each do |m|
75
78
  # belongs_to "parent_#{m}".intern, :foreign_key => 'parent_id', :class_name => m.camelize
@@ -1,4 +1,64 @@
1
1
  module ActiveRecord
2
+ module AssociationPreload
3
+ module ClassMethods
4
+ def find_associated_records(ids, reflection, preload_options)
5
+ options = reflection.options
6
+ table_name = reflection.klass.quoted_table_name
7
+
8
+ if interface = reflection.options[:as]
9
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} IN ('#{self.name}','#{self.base_class.sti_name}')"
10
+ else
11
+ foreign_key = reflection.primary_key_name
12
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
13
+ end
14
+
15
+ conditions << append_conditions(reflection, preload_options)
16
+
17
+ reflection.klass.with_exclusive_scope do
18
+ reflection.klass.find(:all,
19
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
20
+ :include => preload_options[:include] || options[:include],
21
+ :conditions => [conditions, ids],
22
+ :joins => options[:joins],
23
+ :group => preload_options[:group] || options[:group],
24
+ :order => preload_options[:order] || options[:order])
25
+ end
26
+ end
27
+
28
+ def preload_through_records(records, reflection, through_association)
29
+ through_reflection = reflections[through_association]
30
+ through_primary_key = through_reflection.primary_key_name
31
+
32
+ if reflection.options[:source_type]
33
+ interface = reflection.source_reflection.options[:foreign_type]
34
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} IN (?)", reflection.options[:source_type]]}
35
+
36
+ records.compact!
37
+ records.first.class.preload_associations(records, through_association, preload_options)
38
+
39
+ # Dont cache the association - we would only be caching a subset
40
+ through_records = []
41
+ records.each do |record|
42
+ proxy = record.send(through_association)
43
+
44
+ if proxy.respond_to?(:target)
45
+ through_records << proxy.target
46
+ proxy.reset
47
+ else # this is a has_one :through reflection
48
+ through_records << proxy if proxy
49
+ end
50
+ end
51
+ through_records.flatten!
52
+ else
53
+ records.first.class.preload_associations(records, through_association)
54
+ through_records = records.map {|record| record.send(through_association)}.flatten
55
+ end
56
+ through_records.compact!
57
+ through_records
58
+ end
59
+ end
60
+ end
61
+
2
62
  module Associations
3
63
  class HasManyThroughAssociation < HasManyAssociation
4
64
  protected
@@ -74,8 +74,10 @@ module ActsAsJoinable
74
74
 
75
75
  # parent, child, or contexts (both) for custom helper getters/setters
76
76
 
77
- has_many :parent_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :child, :dependent => :destroy, :foreign_key => "child_id", :uniq => true
78
- has_many :child_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :parent, :dependent => :destroy, :foreign_key => "parent_id", :uniq => true
77
+ has_many :parent_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :child, :foreign_key => "child_id", :uniq => true
78
+ has_many :child_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :parent, :foreign_key => "parent_id", :uniq => true
79
+
80
+ after_destroy :destroy_relationships unless after_destroy.map(&:method).include?(:destroy_relationships)
79
81
 
80
82
  related_classes = (ancestors.reverse - included_modules + send(:subclasses)).uniq
81
83
  wanted_classes = []
@@ -103,20 +105,6 @@ module ActsAsJoinable
103
105
  :conditions => sql
104
106
  }
105
107
 
106
- has_both_relationships = relationships.length > 1
107
-
108
- # don't know how to do this with has_many, since the join model requires 2
109
- # different polymorphic models, and it breaks if they're the same class
110
- if has_both_relationships
111
- # you can only get all objects by this, method.
112
- # to create them, use `child_(posts)`, `parent_(posts)`, etc.
113
- # define_method type do
114
- # [:parent, :child].map do |relationship|
115
- # send("#{relationship.to_s}_#{type.to_s}") if respond_to?("#{relationship.to_s}_#{type.to_s}")
116
- # end.flatten.compact.uniq
117
- # end
118
- end
119
-
120
108
  # relationships == [:parent, :child]
121
109
  relationships.each do |relationship|
122
110
  relationship = opposite_for(relationship)
@@ -151,8 +139,7 @@ module ActsAsJoinable
151
139
  through_options = {
152
140
  :class_name => "ActsAsJoinable::Relationship",
153
141
  :conditions => conditions,
154
- :as => opposite_for(relationship).to_sym,
155
- :dependent => :destroy
142
+ :as => opposite_for(relationship).to_sym
156
143
  }
157
144
 
158
145
  through_options[:uniq] = true unless association_type == :has_one
@@ -172,11 +159,9 @@ module ActsAsJoinable
172
159
  options.delete(:uniq) if association_type == :has_one
173
160
 
174
161
  method_scope = association_type == :has_one ? :protected : :public
175
- send(method_scope)
162
+ #send(method_scope)
176
163
  # has_many :child_users, :through => :child_relationships
177
- add_association(relationship.to_s, plural_type, options, join_context, join_value, &block)
178
-
179
- accepts_nested_attributes_for plural_type if nestable
164
+ add_association(relationship.to_s, plural_type, options, join_context, join_value, nestable, &block)
180
165
 
181
166
  if association_type == :has_one
182
167
  define_method singular_type do
@@ -207,7 +192,7 @@ module ActsAsJoinable
207
192
  role.to_s == "parent" ? "child" : "parent"
208
193
  end
209
194
 
210
- def add_association(relationship, plural_type, options, join_context, join_value, &block)
195
+ def add_association(relationship, plural_type, options, join_context, join_value, nestable, &block)
211
196
  eval_options = {:context => join_context}
212
197
  eval_options[:value] = join_value unless join_value.blank?
213
198
  send(:has_many, "#{relationship}_#{plural_type}".to_sym, options) do
@@ -218,20 +203,27 @@ module ActsAsJoinable
218
203
  EOF
219
204
  end
220
205
  # has_many :users, :through => :child_relationships
221
- send(:has_many, plural_type, options) do
222
- class_eval <<-EOF
223
- def construct_join_attributes(associate)
224
- super.merge(#{eval_options.inspect})
225
- end
226
- EOF
206
+
207
+ unless self.reflect_on_all_associations.map(&:name).include?(plural_type.to_sym)
208
+ send(:has_many, plural_type.to_sym, options) do
209
+ class_eval <<-EOF
210
+ def construct_join_attributes(associate)
211
+ super.merge(#{eval_options.inspect})
212
+ end
213
+ EOF
214
+ end
215
+
216
+ accepts_nested_attributes_for plural_type.to_sym if nestable
227
217
  end
228
218
  end
229
219
 
230
220
  end
231
221
 
232
222
  module InstanceMethods
233
-
234
-
223
+ def destroy_relationships
224
+ conditions = %Q|(`relationships`.parent_type IN ("#{self.class.name}","#{self.class.base_class.name}") AND `relationships`.parent_id = #{self.id}) OR (`relationships`.child_type IN ("#{self.class.name}","#{self.class.base_class.name}") AND `relationships`.child_id = #{self.id})|
225
+ ActsAsJoinable::Relationship.delete_all(conditions)
226
+ end
235
227
  end
236
228
  end
237
229
  end
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
- class ActsAsJoinableTest < ActiveSupport::TestCase
3
+ class ActsAsJoinableTest < ActiveRecord::TestCase
4
4
 
5
5
  context "ActsAsJoinable" do
6
6
 
@@ -67,6 +67,25 @@ class ActsAsJoinableTest < ActiveSupport::TestCase
67
67
  assert_equal 1, Asset.count
68
68
  end
69
69
 
70
+ should "have optimized sql calls" do
71
+ ActsAsJoinable::Relationship.delete_all
72
+ group = Group.create!
73
+ group.posts << Post.create!
74
+ group.posts << Post.create!
75
+ assert_queries(1) { Group.all }
76
+ assert_queries(1) { Group.first }
77
+ assert_queries(2) do
78
+ Group.first(:include => [:parent_relationships])
79
+ end
80
+
81
+ # parent, child, and self
82
+ assert_queries(2) do
83
+ group.destroy
84
+ end
85
+
86
+ assert_equal 0, ActsAsJoinable::Relationship.count
87
+ end
88
+
70
89
  teardown do
71
90
  destroy_models
72
91
  end
@@ -26,7 +26,7 @@ require "#{this}/lib/group"
26
26
 
27
27
  ActiveRecord::Base.class_eval do
28
28
  def self.detonate
29
- all.map(&:destroy)
29
+ delete_all
30
30
  end
31
31
  end
32
32
 
@@ -55,4 +55,16 @@ ActiveSupport::TestCase.class_eval do
55
55
  Group.detonate
56
56
  Asset.detonate
57
57
  end
58
+ end
59
+
60
+ ActiveRecord::Base.connection.class.class_eval do
61
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
62
+
63
+ def execute_with_query_record(sql, name = nil, &block)
64
+ $queries_executed ||= []
65
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
66
+ execute_without_query_record(sql, name, &block)
67
+ end
68
+
69
+ alias_method_chain :execute, :query_record
58
70
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-joinable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 6
10
- version: 0.0.6
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Lance Pollard
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-14 00:00:00 -05:00
18
+ date: 2010-09-20 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies: []
21
21