acts_has_many 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +46 -67
- data/lib/acts_has_many/active_record/acts_has_many/child.rb +143 -0
- data/lib/acts_has_many/active_record/acts_has_many/parent.rb +106 -0
- data/lib/acts_has_many/active_record/acts_has_many.rb +100 -0
- data/lib/acts_has_many/version.rb +1 -1
- data/lib/acts_has_many.rb +5 -2
- data/spec/has_many_spec.rb +157 -190
- data/spec/has_many_through_spec.rb +102 -102
- data/spec/helper.rb +1 -0
- metadata +5 -3
- data/lib/acts_has_many/active_record/acts/has_many.rb +0 -358
@@ -1,358 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Acts #:nodoc:
|
3
|
-
module HasMany
|
4
|
-
|
5
|
-
# class methods for use in model
|
6
|
-
# +acts_has_many_for+
|
7
|
-
# +acts_has_many+
|
8
|
-
#
|
9
|
-
# the last method added:
|
10
|
-
#
|
11
|
-
# class method:
|
12
|
-
# +has_many_through_update+
|
13
|
-
# +dependent_relations+
|
14
|
-
# +compare+
|
15
|
-
#
|
16
|
-
# Instance methods:
|
17
|
-
# +model+
|
18
|
-
# +has_many_update+
|
19
|
-
# +has_many_update!+
|
20
|
-
# +update_with_<relation>+
|
21
|
-
# +actuale?+
|
22
|
-
#
|
23
|
-
# set +before_destroy+ callback;
|
24
|
-
|
25
|
-
def self.included base
|
26
|
-
base.extend ClassMethods
|
27
|
-
end
|
28
|
-
|
29
|
-
#
|
30
|
-
# Acts Has Many gem is for added functional to work
|
31
|
-
# with +has_many+ relation (additional is has_many :trhough)
|
32
|
-
#
|
33
|
-
# +acts_has_many+ and +acts_has_many_for+ are class methods for all model,
|
34
|
-
# and you can use them for connection acts_has_many functional to necessary model
|
35
|
-
# acts_has_many set in model where is has_many relation and
|
36
|
-
# acts_has_many_for in model where use model with acts_has_many
|
37
|
-
#
|
38
|
-
# class Education < ActiveRecord::Base
|
39
|
-
# belongs_to :location
|
40
|
-
#
|
41
|
-
# acts_has_many_for :location
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# class Location < ActiveRecord::Base
|
45
|
-
# has_many :educaitons
|
46
|
-
# acts_has_many
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# You can use +acts_has_many+ methods without +acts_has_many_for+
|
50
|
-
#
|
51
|
-
# class Education < ActiveRecord::Base
|
52
|
-
# belongs_to :location
|
53
|
-
#
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# class Location < ActiveRecord::Base
|
57
|
-
# has_many :education
|
58
|
-
# acts_has_many
|
59
|
-
# end
|
60
|
-
#
|
61
|
-
# in this case you can use nex function:
|
62
|
-
#
|
63
|
-
# education = Education.first
|
64
|
-
# location = education.location # get location from education
|
65
|
-
#
|
66
|
-
# new_id, del_id = location.has_many_update(data: {title:"Kyiv"}, relation: :educations)
|
67
|
-
# # or simple
|
68
|
-
# new_id, del_id = location.has_many_update({title:"Kyiv"}, :educations)
|
69
|
-
# # or with dinamic methods helper
|
70
|
-
# new_id, del_id = location.update_with_educations {title:"Kyiv"}
|
71
|
-
#
|
72
|
-
# # you can also use has_many_update! which updated relation with parent row without you
|
73
|
-
# location.has_many_update!({title: "Kyiv"}, education)
|
74
|
-
#
|
75
|
-
# with usage +acts_has_many_for+ you don't need set relation and give parent row in case with has_many_update!
|
76
|
-
# but you need get row for update get per ralation from parent row (see above with location row) in other case
|
77
|
-
# you can't use it becaus methods will not know about relation and paren row for update it.
|
78
|
-
#
|
79
|
-
# new_id, del_id = location.has_many_update {title:"Kyiv"}
|
80
|
-
# # and
|
81
|
-
# location.has_many_update! {title:"Kyiv"}
|
82
|
-
#
|
83
|
-
|
84
|
-
module ClassMethods
|
85
|
-
|
86
|
-
#
|
87
|
-
# +acts_has_many_for+: use with +acts_has_many+
|
88
|
-
# use for set link between parent row and child
|
89
|
-
# options: list relations (symbol)
|
90
|
-
#
|
91
|
-
|
92
|
-
def acts_has_many_for *relations
|
93
|
-
relations.each do |relation|
|
94
|
-
relation = relation.to_s
|
95
|
-
class_eval <<-EOV
|
96
|
-
def #{relation}
|
97
|
-
if #{relation.foreign_key}
|
98
|
-
row = #{relation.classify}.find #{relation.foreign_key}
|
99
|
-
if row.is_a? #{relation.classify}
|
100
|
-
row.tmp_parrent_id = id
|
101
|
-
row.tmp_current_relation = '#{self.name.tableize}'
|
102
|
-
end
|
103
|
-
row
|
104
|
-
else
|
105
|
-
super
|
106
|
-
end
|
107
|
-
end
|
108
|
-
EOV
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
#
|
113
|
-
# +acts_has_many+ - available method in all model and switch on
|
114
|
-
# extension functional in concrete model (need located this method
|
115
|
-
# after relation which include in dependence, or anywhere but set depend relation)
|
116
|
-
# options
|
117
|
-
# :compare( symbol, string)- name column for compare with other element in table
|
118
|
-
# :relations( array) - concrete name of depended relation
|
119
|
-
# :through( boolean) - off or on has_many_through_update method
|
120
|
-
#
|
121
|
-
|
122
|
-
def acts_has_many *opt
|
123
|
-
options = { compare: :title, through: false }
|
124
|
-
options.update opt.extract_options!
|
125
|
-
options.assert_valid_keys :compare, :through, :relations
|
126
|
-
if options[:relations]
|
127
|
-
ActiveSupport::Deprecation.warn "Use simple list relations insted 'relation: Array'! Parameter 'relation: []' will be romoved in v1.0!"
|
128
|
-
end
|
129
|
-
options[:relations] ||= opt
|
130
|
-
|
131
|
-
options[:relations] = self.reflect_on_all_associations(:has_many)
|
132
|
-
.map(&:name) if options[:relations].blank?
|
133
|
-
|
134
|
-
dependent_relations = []
|
135
|
-
options[:relations].each do |relation|
|
136
|
-
if reflect_on_association relation.to_sym
|
137
|
-
dependent_relations << relation.to_s.tableize
|
138
|
-
else
|
139
|
-
raise ArgumentError, "No association found for name `#{relation}'. Has it been defined yet?"
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
#
|
144
|
-
# +has_many_through_update+ (return array) [ 1 - array objects records, 2 - array delete ids ]
|
145
|
-
# options
|
146
|
-
# :update( array) - data for update (id and data)
|
147
|
-
# :new( array) - data for create record (data)
|
148
|
-
#
|
149
|
-
# +for delete need use method destroy !!!+
|
150
|
-
#
|
151
|
-
|
152
|
-
has_many_through = ''
|
153
|
-
if options[:through]
|
154
|
-
has_many_through = """
|
155
|
-
def self.has_many_through_update(options)
|
156
|
-
record_add = []
|
157
|
-
record_del = []
|
158
|
-
|
159
|
-
# update
|
160
|
-
options[:update].each do |id, data|
|
161
|
-
add, del = #{self}.find(id).has_many_update data, options[:relation]
|
162
|
-
record_add << add unless add.nil?
|
163
|
-
record_del << del unless del.nil?
|
164
|
-
end unless options[:update].nil?
|
165
|
-
|
166
|
-
# new
|
167
|
-
unless options[:new].nil?
|
168
|
-
options[:new].uniq!
|
169
|
-
options[:new].each do |data|
|
170
|
-
data = data.symbolize_keys
|
171
|
-
record_add << #{self}
|
172
|
-
.where('#{options[:compare]}' => data['#{options[:compare]}'.to_sym])
|
173
|
-
.first_or_create(data)
|
174
|
-
record_del.delete record_add.last.id
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
record_add = #{self}.where('id IN (?)', record_add) unless record_add.empty?
|
179
|
-
[record_add, record_del]
|
180
|
-
end """
|
181
|
-
end
|
182
|
-
|
183
|
-
# add dinamic methods for example
|
184
|
-
# update_with_<relation>(data) equal has_many_update(data, relation)
|
185
|
-
extend_methods = ''
|
186
|
-
dependent_relations.each do |relation|
|
187
|
-
extend_methods += """
|
188
|
-
def update_with_#{relation}(data)
|
189
|
-
has_many_update data, :#{relation}
|
190
|
-
end
|
191
|
-
"""
|
192
|
-
end
|
193
|
-
|
194
|
-
class_eval <<-EOV
|
195
|
-
include ActiveRecord::Acts::HasMany::InstanceMethods
|
196
|
-
class << self
|
197
|
-
def dependent_relations
|
198
|
-
#{dependent_relations}
|
199
|
-
end
|
200
|
-
def compare
|
201
|
-
'#{options[:compare]}'.to_sym
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def model
|
206
|
-
#{self}
|
207
|
-
end
|
208
|
-
|
209
|
-
#{extend_methods}
|
210
|
-
#{has_many_through}
|
211
|
-
|
212
|
-
attr_accessor :tmp_current_relation, :tmp_parrent_id
|
213
|
-
validates :#{options[:compare]}, uniqueness: true, presence: true
|
214
|
-
before_destroy :destroy_filter
|
215
|
-
EOV
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
module InstanceMethods
|
220
|
-
|
221
|
-
#
|
222
|
-
# +has_many_update!+ identicaly to has_many_update but
|
223
|
-
# You can use this method when you use +acts_has_many_for+
|
224
|
-
# and get object for update with help of +relation+ or give parent row
|
225
|
-
# option
|
226
|
-
# data - date for update
|
227
|
-
# obj - parrent row (maybe miss)
|
228
|
-
#
|
229
|
-
|
230
|
-
def has_many_update! *data
|
231
|
-
if data.size == 2
|
232
|
-
self.tmp_current_relation = data[1].class.name.tableize
|
233
|
-
self.tmp_parrent_id = data[1].id
|
234
|
-
end
|
235
|
-
|
236
|
-
if tmp_current_relation.nil? or tmp_parrent_id.nil?
|
237
|
-
raise ArgumentError, """has_many_update don't have data about parent object,
|
238
|
-
* maybe you use 'acts_has_many_for' incorrectly,
|
239
|
-
* if you don't use 'acts_has_many_for' in parent model you can give parent object"""
|
240
|
-
end
|
241
|
-
|
242
|
-
new_id, del_id = has_many_update data[0]
|
243
|
-
parrent = eval(tmp_current_relation.classify).find tmp_parrent_id
|
244
|
-
parrent.update_attributes("#{model.name.foreign_key}" => new_id)
|
245
|
-
parrent.save!
|
246
|
-
|
247
|
-
destroy unless del_id.nil?
|
248
|
-
model.find new_id
|
249
|
-
end
|
250
|
-
|
251
|
-
#
|
252
|
-
# +has_many_update+ ( return array) [new_id, remove_id]
|
253
|
-
# options maybe Hash, or list parameters
|
254
|
-
# data ( type: hash) - data for updte
|
255
|
-
# relation( type: str, symbol) - modifi with tableize (maybe miss)
|
256
|
-
#
|
257
|
-
|
258
|
-
def has_many_update *options
|
259
|
-
if options.size == 1 && options.first.include?(:data) && options[0].include?(:relation)
|
260
|
-
data = options.first[:data]
|
261
|
-
relation = options.first[:relation]
|
262
|
-
ActiveSupport::Deprecation.warn "Use simple list parameters (data, relation), parameter with 'Hash' type will be romoved in v1.0!"
|
263
|
-
elsif options.size == 2
|
264
|
-
data = options.first
|
265
|
-
relation = options[1]
|
266
|
-
else
|
267
|
-
relation = tmp_current_relation
|
268
|
-
data = options.first
|
269
|
-
end
|
270
|
-
|
271
|
-
data = { model.compare => ''}.merge data
|
272
|
-
|
273
|
-
if relation.blank?
|
274
|
-
warn "[ARRGUMENT MISSING]: 'has_many_update' don't know about current relation, and check all relations"
|
275
|
-
end
|
276
|
-
|
277
|
-
has_many_cleaner data.symbolize_keys, relation
|
278
|
-
end
|
279
|
-
|
280
|
-
#
|
281
|
-
# +actuale?+ - check the acutuality of element in has_many table
|
282
|
-
# options:
|
283
|
-
# relation (String, Symbol) - for exclude current relation
|
284
|
-
#
|
285
|
-
|
286
|
-
def actuale? opt={relation: ""}
|
287
|
-
if opt.is_a? Hash
|
288
|
-
opt.assert_valid_keys :relation
|
289
|
-
ActiveSupport::Deprecation.warn "Use simple parameter 'String' or 'Symbol', parameter with 'Hash' type will be romoved in v1.0!"
|
290
|
-
relation = opt[:relation].to_s.tableize
|
291
|
-
else
|
292
|
-
relation = opt.to_s.tableize
|
293
|
-
end
|
294
|
-
|
295
|
-
actuale = false
|
296
|
-
model.dependent_relations.each do |dependent_relation|
|
297
|
-
tmp = self.send dependent_relation
|
298
|
-
if relation == dependent_relation
|
299
|
-
actuale ||= tmp.all.size > 1
|
300
|
-
else
|
301
|
-
actuale ||= tmp.exists?
|
302
|
-
end
|
303
|
-
end
|
304
|
-
actuale
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
private
|
309
|
-
|
310
|
-
#
|
311
|
-
# +destroy_filter+ - method for before_destroy, check actuale record and
|
312
|
-
# return true for delete or false for leave
|
313
|
-
#
|
314
|
-
|
315
|
-
def destroy_filter
|
316
|
-
not actuale?
|
317
|
-
end
|
318
|
-
|
319
|
-
#
|
320
|
-
# base operations in this gem
|
321
|
-
#
|
322
|
-
|
323
|
-
def has_many_cleaner data, relation
|
324
|
-
compare = { model.compare => data[model.compare] }
|
325
|
-
|
326
|
-
object_id = id
|
327
|
-
delete_id = nil
|
328
|
-
|
329
|
-
if actuale? relation
|
330
|
-
# create new object and finish
|
331
|
-
object = model.where(compare).first_or_create data
|
332
|
-
object_id = object.id
|
333
|
-
else
|
334
|
-
object_tmp = model.where(compare).first
|
335
|
-
unless object_tmp.nil?
|
336
|
-
# set new object and delete old
|
337
|
-
delete_id = (object_id == object_tmp.id) ? nil : object_id
|
338
|
-
object_id = object_tmp.id
|
339
|
-
else
|
340
|
-
# update old object
|
341
|
-
if object_id.nil?
|
342
|
-
object = model.where(compare).first_or_create data
|
343
|
-
object_id = object.id
|
344
|
-
else
|
345
|
-
if data[model.compare].blank?
|
346
|
-
delete_id = object_id
|
347
|
-
object_id = nil
|
348
|
-
else
|
349
|
-
update_attributes data
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|
353
|
-
end
|
354
|
-
[object_id, delete_id]
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end
|