joinfix 0.1.1 → 1.0.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.
data/lib/joinfix.rb CHANGED
@@ -3,26 +3,34 @@ require 'active_record/fixtures'
3
3
  require 'joinfix/fixtures_class'
4
4
  require 'joinfix/fixtures'
5
5
  require 'joinfix/fixture'
6
+ require 'joinfix/error'
6
7
 
7
- # The actual JoinFix module contains methods managing joins. In practice, JoinFix
8
- # extends hashes that contain the entry data. Calls to the join_(type) methods define
9
- # join references like ['child_id', 'child_table'] => 'child_entry_name'. This reference
10
- # specifies that 'child_id' should be set to the primary key of 'child_entry_name' in
11
- # 'child_table'.
8
+ # The JoinFix module contains methods to make joins. In practice, JoinFix
9
+ # extends hashes that contain the entry data. Joins are created by calling the
10
+ # join_(macro) methods, which creates join references like:
12
11
  #
13
- # The join methods each take a child entry and a config. The instance calling the join
14
- # method is considered the parent entry. References are added where appropriate.
15
- # A separate join entry will be returned if required, as for has_and_belongs_to_many
16
- # or has_many :through.
12
+ # ['child_id', 'child_table'] => 'child_entry_name'
17
13
  #
18
- # The configurations required by the join methods can be created from an ActiveRecord::Base
19
- # class and the name of the association through the +configure+ method.
14
+ # Later on these references are resolved so that 'child_id' will be set to
15
+ # the primary key of 'child_entry_name' in 'child_table'.
16
+ #
17
+ # The hash extended by the JoinFix module is considered the parent entry. Each
18
+ # join_(macro) method takes a child entry and a config specifiying details of the join.
19
+ # A separate join entry will be returned if required (as for 'has_and_belongs_to_many'
20
+ # or 'has_many :through'). The configurations required by the join methods can be
21
+ # created by providing an ActiveRecord::Base class and association name to
22
+ # +configure+.
23
+ #
24
+ # A little terminology:
25
+ # macro:: Refers to the join macro (ex :belongs_to, :has_many)
26
+ # associable:: The association name in a belongs_to :polymorphic association
27
+ # associable_type:: The field holding the class name of the associated entry (ie "#{association.name}_type") in a belongs_to :polymorphic association
20
28
  module JoinFix
21
29
  class << self
22
30
  # Returns true if the input macro allows for multiple joins.
23
31
  #
24
- # [:belongs_to, :has_one] => false
25
- # [:has_many, :has_and_belongs_to_many] => true
32
+ # [:belongs_to, :has_one] => false
33
+ # [:has_many, :has_and_belongs_to_many] => true
26
34
  def macro_allows_multiple(macro)
27
35
  case macro.to_sym
28
36
  when :belongs_to then false
@@ -32,23 +40,22 @@ module JoinFix
32
40
  end
33
41
  end
34
42
 
35
- def configure(klass, assoc_name)
36
- association = association(klass, assoc_name)
37
- send("configure_#{association.macro}", klass, association).with_indifferent_access
38
- end
39
-
40
- protected
41
-
42
- def association(klass, assoc_name)
43
- association = klass.reflect_on_association(assoc_name.to_sym)
44
- raise ArgumentError, "Unknown association '#{assoc_name}' for '#{klass}'." unless association
45
- association.check_validity!
46
- association
43
+ # Constructs a join configuration for the specified assocation.
44
+ def configure(active_record_class, assoc_name)
45
+ association = association(active_record_class, assoc_name)
46
+ send("configure_#{association.macro}", active_record_class, association).with_indifferent_access
47
47
  end
48
-
49
- def configure_belongs_to(klass, association)
48
+
49
+ # Returns a hash of configurations for a belongs_to association.
50
+ #
51
+ # Returns configurations:
52
+ # [:macro,:parent_table, :foreign_key, :child_table]
53
+ #
54
+ # Returned configurations for belongs_to :polymorphic
55
+ # [:macro, parent_table, :foreign_key, :polymorphic (=true), :associable, :associable_type]
56
+ def configure_belongs_to(active_record_class, association)
50
57
  join_config = {
51
- :parent_table => klass.table_name,
58
+ :parent_table => active_record_class.table_name,
52
59
  :macro => association.macro,
53
60
  :foreign_key => association.primary_key_name
54
61
  }
@@ -64,9 +71,13 @@ module JoinFix
64
71
  join_config
65
72
  end
66
73
 
67
- def configure_has_one(klass, association)
74
+ # Returns a hash of configurations for a has_one association
75
+ #
76
+ # Returns configurations:
77
+ # [:macro, :parent_table, :child_table, :foreign_key]
78
+ def configure_has_one(active_record_class, association)
68
79
  join_config = {
69
- :parent_table => klass.table_name,
80
+ :parent_table => active_record_class.table_name,
70
81
  :child_table => association.table_name,
71
82
  :macro => association.macro,
72
83
  :foreign_key => association.primary_key_name
@@ -75,9 +86,19 @@ module JoinFix
75
86
  join_config
76
87
  end
77
88
 
78
- def configure_has_many(klass, association)
89
+ # Returns a hash of configurations for a has_many association
90
+ #
91
+ # Returns configurations:
92
+ # [:macro, :parent_table, :child_table, :foreign_key]
93
+ #
94
+ # Returned configurations for has_many :as
95
+ # [:macro, :parent_table, :child_table, :foreign_key, :as, :parent_class]
96
+ #
97
+ # Returned configurations for has_many :through
98
+ # [:macro, :parent_table, :child_table, :through, :join_table, :foreign_key, :association_foreign_key]
99
+ def configure_has_many(active_record_class, association)
79
100
  join_config = {
80
- :parent_table => klass.table_name,
101
+ :parent_table => active_record_class.table_name,
81
102
  :child_table => association.table_name,
82
103
  :macro => association.macro,
83
104
  :foreign_key => association.primary_key_name
@@ -85,7 +106,7 @@ module JoinFix
85
106
 
86
107
  if association.options[:as]
87
108
  join_config[:as] = association.options[:as]
88
- join_config[:parent_class] = klass.class_name
109
+ join_config[:parent_class] = active_record_class.class_name
89
110
  elsif association.options[:through]
90
111
  join_config[:through] = association.options[:through]
91
112
  join_config[:join_table] = association.through_reflection.table_name
@@ -96,9 +117,13 @@ module JoinFix
96
117
  join_config
97
118
  end
98
119
 
99
- def configure_has_and_belongs_to_many(klass, association)
120
+ # Returns a hash of configurations for a has_and_belongs_to_many association
121
+ #
122
+ # Returns configurations:
123
+ # [:macro, :parent_table, :child_table, :join_table, :foreign_key, :association_foreign_key]
124
+ def configure_has_and_belongs_to_many(active_record_class, association)
100
125
  join_config = {
101
- :parent_table => klass.table_name,
126
+ :parent_table => active_record_class.table_name,
102
127
  :child_table => association.table_name,
103
128
  :macro => association.macro,
104
129
  :join_table => association.options[:join_table],
@@ -108,37 +133,46 @@ module JoinFix
108
133
 
109
134
  join_config
110
135
  end
136
+
137
+ protected
138
+
139
+ # Looks up the association and checks that the association is valid.
140
+ def association(active_record_class, assoc_name) # :nodoc:
141
+ association = active_record_class.reflect_on_association(assoc_name.to_sym)
142
+ raise ArgumentError, "Unknown association '#{assoc_name}' for '#{active_record_class}'." unless association
143
+ association.check_validity!
144
+ association
145
+ end
111
146
  end
112
147
 
113
- # Convenience method for setting up a JoinFix.
148
+ attr_accessor :entry_name
149
+
150
+ # Convenience method for setting up a JoinFix. Simply takes the input hash extends with
151
+ # JoinFix and sets the entry_name.
114
152
  def self.new(entry_name, hash={})
115
153
  hash.extend JoinFix
116
154
  hash.entry_name = entry_name
117
155
  hash
118
156
  end
119
157
 
120
- attr_accessor :entry_name
121
-
122
- # extract_if extracts values where the key is specified
123
- # or returns true when passed to the optional block.
124
- def extract_if(keys=[], &block)
158
+ # Extracts values if the key is specified in keys or if the block returns true.
159
+ def extract_if(keys=[], &block) # :yields: key, value
125
160
  extract(:if, keys, block)
126
161
  end
127
162
 
128
- # extract_unless extracts values where the key is not specified
129
- # or does not return true when passed to the optional block.
130
- def extract_unless(keys=[], &block)
163
+ # Extracts values unless the key is specified in keys or if the block returns false.
164
+ def extract_unless(keys=[], &block) # :yields: key, value
131
165
  extract(:unless, keys, block)
132
166
  end
133
167
 
134
- # Creates the required key-value pairs for a 'belongs_to' association between the fixture
135
- # and the input entry. Returns nil
168
+ # Creates the required key-value pairs for a 'belongs_to' association between self
169
+ # and the input entry. Returns nil.
136
170
  #
137
171
  # Required configurations:
138
- # * [:foreign_key, :child_table]
172
+ # [:foreign_key, :child_table]
139
173
  #
140
174
  # Required configurations for belongs_to :polymorphic
141
- # * [:polymorphic (=true), :associable, :foreign_key, :child_table, :child_class]
175
+ # [:associable, :child_table, :child_class, :polymorphic (=true), :foreign_key]
142
176
  def join_belongs_to(entry, config)
143
177
  if config[:polymorphic]
144
178
  # produce entries like:
@@ -162,11 +196,11 @@ module JoinFix
162
196
  nil
163
197
  end
164
198
 
165
- # Creates the required key-value pairs for a 'has_one' association between the fixture
166
- # and the input entry. Returns nil
199
+ # Creates the required key-value pairs for a 'has_one' association between self
200
+ # and the input entry. Returns nil.
167
201
  #
168
202
  # Required configurations:
169
- # * [:foreign_key, :parent_table]
203
+ # [:parent_table, :foreign_key]
170
204
  def join_has_one(entry, config)
171
205
  # produces entries like:
172
206
  # entry[foreign_key_ref_parent_table] = self.entry_name
@@ -178,41 +212,18 @@ module JoinFix
178
212
  nil
179
213
  end
180
214
 
181
- # Creates the required key-value pairs for a 'has_and_belongs_to_many' association between
182
- # the fixture and the input entry. Returns a join entry.
183
- #
184
- # Required configurations:
185
- # * [:foreign_key, :association_foreign_key, :parent_table, :child_table]
186
- def join_has_and_belongs_to_many(entry, config)
187
- # produces entries like:
188
- # join[foreign_key_ref_parent_table] = self.entry_name
189
- # join[association_foreign_key_ref_child_table] = entry.entry_name
190
- # later resolved to:
191
- # join[foreign_key_id] = self.id
192
- # join[association_foreign_key_id] = entry.id
193
- validate_inclusion(config, :foreign_key, :association_foreign_key, :parent_table, :child_table)
194
-
195
- join_entry_name = self.entry_name < entry.entry_name ?
196
- "#{self.entry_name}_#{entry.entry_name}" :
197
- "#{entry.entry_name}_#{self.entry_name}"
198
-
199
- JoinFix.new join_entry_name,
200
- [config[:foreign_key], config[:parent_table]] => self.entry_name,
201
- [config[:association_foreign_key], config[:child_table]] => entry.entry_name
202
- end
203
-
204
- # Creates the required key-value pairs for a 'has_many' association between
205
- # the fixture and the input entry. Returns a join entry if configuration :through is
215
+ # Creates the required key-value pairs for a 'has_many' association between
216
+ # self and the input entry. Returns a join entry if configuration :through is
206
217
  # specified, otherwise returns nil.
207
218
  #
208
219
  # Required configurations:
209
- # * [:foreign_key, :parent_table]
220
+ # [:parent_table, :foreign_key]
210
221
  #
211
222
  # Required configurations for has_many :as
212
- # * [:as (=association), :parent_table]
223
+ # [:parent_table, :as (=association)]
213
224
  #
214
225
  # Required configurations for has_many :through
215
- # * [:through (=join table), :foreign_key, :association_foreign_key, :parent_table, :child_table]
226
+ # [:parent_table, :child_table, :through (=join table), :foreign_key, :association_foreign_key]
216
227
  def join_has_many(entry, config)
217
228
  if config[:as]
218
229
  # produces entries like:
@@ -235,18 +246,41 @@ module JoinFix
235
246
  join_has_one(entry, config)
236
247
  end
237
248
  end
249
+
250
+ # Creates the required key-value pairs for a 'has_and_belongs_to_many' association between
251
+ # self and the input entry. Returns a join entry.
252
+ #
253
+ # Required configurations:
254
+ # [:parent_table, :child_table, :foreign_key, :association_foreign_key]
255
+ def join_has_and_belongs_to_many(entry, config)
256
+ # produces entries like:
257
+ # join[foreign_key_ref_parent_table] = self.entry_name
258
+ # join[association_foreign_key_ref_child_table] = entry.entry_name
259
+ # later resolved to:
260
+ # join[foreign_key_id] = self.id
261
+ # join[association_foreign_key_id] = entry.id
262
+ validate_inclusion(config, :foreign_key, :association_foreign_key, :parent_table, :child_table)
263
+
264
+ join_entry_name = self.entry_name < entry.entry_name ?
265
+ "#{self.entry_name}_#{entry.entry_name}" :
266
+ "#{entry.entry_name}_#{self.entry_name}"
267
+
268
+ JoinFix.new join_entry_name,
269
+ [config[:foreign_key], config[:parent_table]] => self.entry_name,
270
+ [config[:association_foreign_key], config[:child_table]] => entry.entry_name
271
+ end
238
272
 
239
273
  protected
240
274
 
241
- # Checks each input type is present in the config; raises an error when it is not.
242
- def validate_inclusion(config, *types)
243
- types.each do |type|
244
- raise ArgumentError, "Missing configuration '#{type}'" unless config.has_key?(type)
275
+ # Checks each key is present in the config; raises an error when it is not.
276
+ def validate_inclusion(config, *keys) # :nodoc:
277
+ keys.each do |key|
278
+ raise ArgumentError, "Missing configuration '#{key}'" unless config.has_key?(key)
245
279
  end
246
280
  end
247
281
 
248
282
  # The generalized extraction method called by extract_if and extract_unless
249
- def extract(method, keys, block)
283
+ def extract(method, keys, block) # :nodoc:
250
284
  # be sure that allowed keys is an array, to respond to include?
251
285
  keys = [keys] unless keys.kind_of?(Array)
252
286
 
@@ -1,4 +1,4 @@
1
- class Group < ActiveRecord::Base
2
- has_many :group_users, :class_name => 'UserGroup'
3
- has_many :users, :through => :group_users
1
+ class Group < ActiveRecord::Base
2
+ has_many :user_groups
3
+ has_many :users, :through => :user_groups
4
4
  end
@@ -1,7 +1,7 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.column :login, :string
4
+ t.column :full_name, :string
5
5
  end
6
6
  end
7
7
 
data/rails/db/schema.rb CHANGED
@@ -29,7 +29,7 @@ ActiveRecord::Schema.define(:version => 6) do
29
29
  end
30
30
 
31
31
  create_table "users", :force => true do |t|
32
- t.column "login", :string
32
+ t.column "full_name", :string
33
33
  end
34
34
 
35
35
  end