acts-as-joinable 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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