amoeba 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -1,15 +1,15 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "amoeba/version"
2
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
+ require 'amoeba/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "amoeba"
6
+ s.name = 'amoeba'
7
7
  s.version = Amoeba::VERSION
8
- s.authors = ["Vaughn Draughon"]
9
- s.email = "vaughn@rocksolidwebdesign.com"
10
- s.homepage = "http://github.com/rocksolidwebdesign/amoeba"
11
- s.license = "BSD"
12
- s.summary = %q{Easy copying of rails models and their child associations.}
8
+ s.authors = ['Vaughn Draughon']
9
+ s.email = 'vaughn@rocksolidwebdesign.com'
10
+ s.homepage = 'http://github.com/rocksolidwebdesign/amoeba'
11
+ s.license = 'BSD'
12
+ s.summary = 'Easy copying of rails models and their child associations. '
13
13
 
14
14
  s.description = <<-EOF
15
15
  An extension to ActiveRecord to allow the duplication method to also copy associated children, with recursive support for nested of grandchildren. The behavior is controllable with a simple DSL both on your rails models and on the fly, i.e. per instance. Numerous configuration styles and preprocessing directives are included for power and flexibility. Supports preprocessing of field values to prepend strings such as "Copy of ", to nullify or process field values with regular expressions. Supports most association types including has_one :through and has_many :through.
@@ -17,18 +17,23 @@ An extension to ActiveRecord to allow the duplication method to also copy associ
17
17
  Tags: copy child associations, copy nested children, copy associated child records, nested copy, copy associations, copy relations, copy relationships, duplicate associations, duplicate associated records, duplicate child records, duplicate children, copy all, duplicate all, clone child associations, clone nested children, clone associated child records, nested clone, clone associations, clone relations, clone relationships, cloning child associations, cloning nested children, cloning associated child records, deep_cloning, nested cloning, cloning associations, cloning relations, cloning relationships, cloning child associations, cloning nested children, cloning associated child records, nested cloning, cloning associations, cloning relations, cloning relationships, cloning child associations, cloning nested children, cloning associated child records, deep_cloning, nested cloning, cloning associations, cloning relations, cloning relationships, duplicate child associations, duplicate nested children, duplicate associated child records, nested duplicate, duplicate associations, duplicate relations, duplicate relationships, duplicate child associations, duplicate nested children, duplicate associated child records, deep_duplicate, nested duplicate, duplicate associations, duplicate relations, duplicate relationships, deep_copy, deep_clone, deep_cloning, deep clone, deep cloning, has_one, has_many, has_and_belongs_to_many
18
18
  EOF
19
19
 
20
- s.rubyforge_project = "amoeba"
20
+ s.rubyforge_project = 'amoeba'
21
21
 
22
22
  s.files = `git ls-files`.split("\n")
23
23
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
- s.require_paths = ["lib"]
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
25
+ s.require_paths = ['lib']
26
26
 
27
27
  # specify any dependencies here; for example:
28
- s.add_development_dependency "bundler", ">= 1.0.0"
29
- s.add_development_dependency "rspec", "~> 2.3"
28
+ s.add_development_dependency 'bundler', '>= 1.6.0'
29
+ s.add_development_dependency 'rspec', '>= 3.0.0'
30
30
 
31
- s.add_development_dependency "sqlite3"
31
+ if RUBY_PLATFORM == 'java'
32
+ s.add_development_dependency 'activerecord-jdbc-adapter', '~> 1.3.2'
33
+ s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.3.2'
34
+ else
35
+ s.add_development_dependency 'sqlite3'
36
+ end
32
37
 
33
- s.add_dependency 'activerecord', '>= 3.2.6', '< 5'
38
+ s.add_dependency 'activerecord', '>= 3.2.6'
34
39
  end
@@ -0,0 +1,11 @@
1
+ ---
2
+ NestedIterators:
3
+ max_allowed_nesting: 2
4
+ UtilityFunction:
5
+ enabled: false
6
+ IrresponsibleModule:
7
+ enabled: false
8
+ DuplicateMethodCall:
9
+ max_calls: 5
10
+ FeatureEnvy:
11
+ enabled: false
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.0"
6
+
7
+ group :development, :test do
8
+ gem "rake"
9
+ gem "coveralls", :require => false
10
+ end
11
+
12
+ group :local_development do
13
+ gem "pry"
14
+ gem "appraisal"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.0"
6
+
7
+ group :development, :test do
8
+ gem "rake"
9
+ gem "coveralls", :require => false
10
+ end
11
+
12
+ group :local_development do
13
+ gem "pry"
14
+ gem "appraisal"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.0"
6
+
7
+ group :development, :test do
8
+ gem "rake"
9
+ gem "coveralls", :require => false
10
+ end
11
+
12
+ group :local_development do
13
+ gem "pry"
14
+ gem "appraisal"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.0"
6
+
7
+ group :development, :test do
8
+ gem "rake"
9
+ gem "coveralls", :require => false
10
+ end
11
+
12
+ group :local_development do
13
+ gem "pry"
14
+ gem "appraisal"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -0,0 +1,23 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git "git://github.com/rails/arel.git" do
6
+ gem "arel"
7
+ end
8
+
9
+ git "git://github.com/rails/rails.git" do
10
+ gem "activerecord"
11
+ end
12
+
13
+ group :development, :test do
14
+ gem "rake"
15
+ gem "coveralls", :require => false
16
+ end
17
+
18
+ group :local_development do
19
+ gem "pry"
20
+ gem "appraisal"
21
+ end
22
+
23
+ gemspec :path => "../"
@@ -1,527 +1,18 @@
1
- require "active_record"
2
- require "amoeba/version"
1
+ require 'active_record'
2
+ require 'active_support/all'
3
+ require 'amoeba/version'
4
+ require 'amoeba/config'
5
+ require 'amoeba/macros'
6
+ require 'amoeba/macros/base'
7
+ require 'amoeba/macros/has_many'
8
+ require 'amoeba/macros/has_one'
9
+ require 'amoeba/macros/has_and_belongs_to_many'
10
+ require 'amoeba/cloner'
11
+ require 'amoeba/class_methods'
12
+ require 'amoeba/instance_methods'
3
13
 
4
14
  module Amoeba
5
- module Dsl # {{{
6
- class Config
7
- def initialize(parent)
8
- @parent = parent
9
- end
10
-
11
- # Getters {{{
12
- def upbringing
13
- @raised ||= false
14
- @raised
15
- end
16
-
17
- def enabled
18
- @enabled ||= false
19
- @enabled
20
- end
21
-
22
- def inherit
23
- @inherit ||= false
24
- @inherit
25
- end
26
-
27
- def do_preproc
28
- @do_preproc ||= false
29
- @do_preproc
30
- end
31
-
32
- def parenting
33
- @parenting ||= false
34
- @parenting
35
- end
36
-
37
- def known_macros
38
- @known_macros ||= [:has_one, :has_many, :has_and_belongs_to_many]
39
- @known_macros
40
- end
41
-
42
- def includes
43
- @includes ||= []
44
- @includes
45
- end
46
-
47
- def excludes
48
- @excludes ||= []
49
- @excludes
50
- end
51
-
52
- def clones
53
- @clones ||= []
54
- @clones
55
- end
56
-
57
- def customizations
58
- @customizations ||= []
59
- @customizations
60
- end
61
-
62
- def overrides
63
- @overrides ||= []
64
- @overrides
65
- end
66
-
67
- def null_fields
68
- @null_fields ||= []
69
- @null_fields
70
- end
71
-
72
- def coercions
73
- @coercions ||= {}
74
- @coercions
75
- end
76
-
77
- def prefixes
78
- @prefixes ||= {}
79
- @prefixes
80
- end
81
-
82
- def suffixes
83
- @suffixes ||= {}
84
- @suffixes
85
- end
86
-
87
- def regexes
88
- @regexes ||= {}
89
- @regexes
90
- end
91
- # }}}
92
-
93
- # Setters (Config DSL) {{{
94
- def enable
95
- @enabled = true
96
- end
97
-
98
- def disable
99
- @enabled = false
100
- end
101
-
102
- def raised(style=:submissive)
103
- @raised = style
104
- end
105
-
106
- def propagate(style=:submissive)
107
- @parenting ||= style
108
- @inherit = true
109
- end
110
-
111
- def include_field(value=nil)
112
- @enabled ||= true
113
- @excludes = []
114
- @includes ||= []
115
- if value.is_a?(Array)
116
- @includes = value
117
- else
118
- @includes << value if value
119
- end
120
- @includes
121
- end
122
-
123
- def exclude_field(value=nil)
124
- @enabled ||= true
125
- @includes = []
126
- @excludes ||= []
127
- if value.is_a?(Array)
128
- @excludes = value
129
- else
130
- @excludes << value if value
131
- end
132
- @excludes
133
- end
134
-
135
- def clone(value=nil)
136
- @enabled ||= true
137
- @clones ||= []
138
- if value.is_a?(Array)
139
- @clones = value
140
- else
141
- @clones << value if value
142
- end
143
- @clones
144
- end
145
-
146
- def override(value=nil)
147
- @do_preproc ||= true
148
- @overrides ||= []
149
- if value.is_a?(Array)
150
- @overrides = value
151
- else
152
- @overrides << value if value
153
- end
154
- @overrides
155
- end
156
-
157
- def customize(value=nil)
158
- @do_preproc ||= true
159
- @customizations ||= []
160
- if value.is_a?(Array)
161
- @customizations = value
162
- else
163
- @customizations << value if value
164
- end
165
- @customizations
166
- end
167
-
168
- def recognize(value=nil)
169
- @enabled ||= true
170
- @known_macros ||= []
171
- if value.is_a?(Array)
172
- @known_macros = value
173
- else
174
- @known_macros << value if value
175
- end
176
- @known_macros
177
- end
178
-
179
- def nullify(value=nil)
180
- @do_preproc ||= true
181
- @null_fields ||= []
182
- if value.is_a?(Array)
183
- @null_fields = value
184
- else
185
- @null_fields << value if value
186
- end
187
- @null_fields
188
- end
189
-
190
- def set(defs=nil)
191
- @do_preproc ||= true
192
- @coercions ||= {}
193
- if defs.is_a?(Array)
194
- @coercions = {}
195
-
196
- defs.each do |d|
197
- d.each do |k,v|
198
- @coercions[k] = v if v
199
- end
200
- end
201
- else
202
- defs.each do |k,v|
203
- @coercions[k] = v if v
204
- end
205
- end
206
- @coercions
207
- end
208
-
209
- def prepend(defs=nil)
210
- @do_preproc ||= true
211
- @prefixes ||= {}
212
- if defs.is_a?(Array)
213
- @prefixes = {}
214
-
215
- defs.each do |d|
216
- d.each do |k,v|
217
- @prefixes[k] = v if v
218
- end
219
- end
220
- else
221
- defs.each do |k,v|
222
- @prefixes[k] = v if v
223
- end
224
- end
225
- @prefixes
226
- end
227
-
228
- def append(defs=nil)
229
- @do_preproc ||= true
230
- @suffixes ||= {}
231
- if defs.is_a?(Array)
232
- @suffixes = {}
233
-
234
- defs.each do |d|
235
- d.each do |k,v|
236
- @suffixes[k] = v if v
237
- end
238
- end
239
- else
240
- defs.each do |k,v|
241
- @suffixes[k] = v if v
242
- end
243
- end
244
- @suffixes
245
- end
246
-
247
- def regex(defs=nil)
248
- @do_preproc ||= true
249
- @regexes ||= {}
250
- if defs.is_a?(Array)
251
- @regexes = {}
252
-
253
- defs.each do |d|
254
- d.each do |k,v|
255
- @regexes[k] = v if v
256
- end
257
- end
258
- else
259
- defs.each do |k,v|
260
- @regexes[k] = v if v
261
- end
262
- end
263
- @regexes
264
- end
265
- # }}}
266
- end
267
- end # }}}
268
-
269
- module ClassMethods
270
- def amoeba(&block)
271
- @config_block ||= block if block_given?
272
-
273
- @config ||= Amoeba::Dsl::Config.new(self)
274
- @config.instance_eval(&block) if block_given?
275
- @config
276
- end
277
-
278
- def fresh_amoeba(&block)
279
- @config_block = block if block_given?
280
-
281
- @config = Amoeba::Dsl::Config.new(self)
282
- @config.instance_eval(&block) if block_given?
283
- @config
284
- end
285
-
286
- def amoeba_block
287
- @config_block
288
- end
289
- end
290
-
291
- module InstanceMethods
292
- # Config Getters {{{
293
- def amoeba_conf
294
- self.class.amoeba
295
- end
296
-
297
- def amoeba_dup_options
298
- @amoeba_dup_options
299
- end
300
-
301
- def has_parent_amoeba_conf?
302
- self.class.superclass.respond_to?(:amoeba)
303
- end
304
-
305
- def parent_amoeba_conf
306
- if has_parent_amoeba_conf?
307
- self.class.superclass.amoeba
308
- else
309
- false
310
- end
311
- end
312
-
313
- def amoeba_settings
314
- self.class.amoeba_block
315
- end
316
-
317
- def has_parent_amoeba_settings?
318
- self.class.superclass.respond_to?(:amoeba_block)
319
- end
320
-
321
- def parent_amoeba_settings
322
- if has_parent_amoeba_conf?
323
- self.class.superclass.amoeba_block
324
- else
325
- false
326
- end
327
- end
328
- # }}}
329
-
330
- def amoeba_dup(options={})
331
- @result = self.dup()
332
- @amoeba_dup_options = options
333
-
334
- # Inherit Parent Settings {{{
335
- if !amoeba_conf.enabled && parent_amoeba_conf.inherit
336
- if amoeba_conf.upbringing
337
- parenting_style = amoeba_conf.upbringing
338
- else
339
- parenting_style = parent_amoeba_conf.parenting
340
- end
341
-
342
- case parenting_style
343
- when :strict
344
- # parent settings only
345
- self.class.fresh_amoeba(&parent_amoeba_settings)
346
- when :relaxed
347
- # parent takes precedence
348
- self.class.amoeba(&parent_amoeba_settings)
349
- when :submissive
350
- # parent suggests things
351
- # child does what it wants to anyway
352
- child_settings = amoeba_settings
353
- self.class.fresh_amoeba(&parent_amoeba_settings)
354
- self.class.amoeba(&child_settings)
355
- end
356
- end
357
- # }}}
358
-
359
- # Run Amoeba {{{
360
- # pramoeba_conf.overridesepend any extra strings to indicate uniqueness of the new record(s)
361
- if amoeba_conf.overrides.length > 0
362
- amoeba_conf.overrides.each do |block|
363
- block.call(self, @result)
364
- end
365
- end
366
-
367
- if amoeba_conf.enabled
368
- # Deep Clone Settings {{{
369
- amoeba_conf.clones.each do |clone_field|
370
- r = self.class.reflect_on_association clone_field
371
-
372
- # if this is a has many through and we're gonna deep
373
- # copy the child records, exclude the regular join
374
- # table from copying so we don't end up with the new
375
- # and old children on the copy
376
- if r.macro == :has_many && r.is_a?(ActiveRecord::Reflection::ThroughReflection)
377
- amoeba_conf.exclude_field r.options[:through]
378
- end
379
- end
380
- # }}}
381
-
382
- # Inclusive Style {{{
383
- if amoeba_conf.includes.count > 0
384
- amoeba_conf.includes.each do |i|
385
- r = self.class.reflect_on_association i
386
- amo_process_association(i, r)
387
- end
388
- # }}}
389
- # Exclusive Style {{{
390
- elsif amoeba_conf.excludes.count > 0
391
- self.class.reflections.each do |r|
392
- if not amoeba_conf.excludes.include?(r[0])
393
- amo_process_association(r[0], r[1])
394
- end
395
- end
396
- # }}}
397
- # Indiscriminate Style {{{
398
- else
399
- self.class.reflections.each do |r|
400
- amo_process_association(r[0], r[1])
401
- end
402
- end
403
- # }}}
404
- end
405
-
406
- if amoeba_conf.do_preproc
407
- amo_preprocess_parent_copy
408
- end
409
- # }}}
410
-
411
- @result
412
- end
413
-
414
- private
415
- # Copy Children {{{
416
- def amo_process_association(relation_name, settings)
417
- if not amoeba_conf.known_macros.include?(settings.macro)
418
- return
419
- end
420
-
421
- case settings.macro
422
- when :has_one
423
- if settings.is_a?(ActiveRecord::Reflection::ThroughReflection)
424
- return
425
- end
426
-
427
- old_obj = self.send(relation_name)
428
-
429
- if not old_obj.nil?
430
- copy_of_obj = old_obj.amoeba_dup(amoeba_dup_options)
431
- copy_of_obj[:"#{settings.foreign_key}"] = nil
432
-
433
- @result.send(:"#{relation_name}=", copy_of_obj)
434
- end
435
- when :has_many
436
- clone = amoeba_conf.clones.include?(:"#{relation_name}")
437
-
438
- # this could be DRYed up for better readability by
439
- # duplicating the loop code, but I'm duplicating the
440
- # loops to avoid that extra check on each iteration
441
- if clone
442
- # This is a M:M "has many through" where we
443
- # actually copy and reassociate the new children
444
- # rather than only maintaining the associations
445
- self.send(relation_name).each do |old_obj|
446
-
447
- copy_of_obj = old_obj.amoeba_dup
448
-
449
- # associate this new child to the new parent object
450
- @result.send(relation_name) << copy_of_obj
451
- end
452
- else
453
- # This is a regular 1:M "has many"
454
- #
455
- # copying the children of the regular has many will
456
- # effectively do what is desired anyway, the through
457
- # association is really just for convenience usage
458
- # on the model
459
- return if settings.is_a?(ActiveRecord::Reflection::ThroughReflection)
460
-
461
- self.send(relation_name).each do |old_obj|
462
- copy_of_obj = old_obj.amoeba_dup(amoeba_dup_options)
463
- copy_of_obj[:"#{settings.foreign_key}"] = nil
464
-
465
- # associate this new child to the new parent object
466
- @result.send(relation_name) << copy_of_obj
467
- end
468
- end
469
-
470
- when :has_and_belongs_to_many
471
- clone = amoeba_conf.clones.include?(relation_name)
472
-
473
- if clone
474
- self.send(relation_name).each do |old_obj|
475
- copy_of_obj = old_obj.amoeba_dup
476
-
477
- # associate this new child to the new parent object
478
- @result.send(relation_name) << copy_of_obj
479
- end
480
- else
481
- self.send(relation_name).each do |old_obj|
482
- # associate this new child to the new parent object
483
- @result.send(relation_name) << old_obj
484
- end
485
- end
486
- end
487
- end
488
- # }}}
489
-
490
- # Field Preprocessor {{{
491
- def amo_preprocess_parent_copy
492
- # nullify any fields the user has configured
493
- amoeba_conf.null_fields.each do |n|
494
- @result[n] = nil
495
- end
496
-
497
- # prepend any extra strings to indicate uniqueness of the new record(s)
498
- amoeba_conf.coercions.each do |field,coercion|
499
- @result[field] = "#{coercion}"
500
- end
501
-
502
- # prepend any extra strings to indicate uniqueness of the new record(s)
503
- amoeba_conf.prefixes.each do |field,prefix|
504
- @result[field] = "#{prefix}#{@result[field]}"
505
- end
506
-
507
- # postpend any extra strings to indicate uniqueness of the new record(s)
508
- amoeba_conf.suffixes.each do |field,suffix|
509
- @result[field] = "#{@result[field]}#{suffix}"
510
- end
511
-
512
- # regex andy fields that need changing
513
- amoeba_conf.regexes.each do |field,action|
514
- @result[field].gsub!(action[:replace], action[:with])
515
- end
516
-
517
- # prepend any extra strings to indicate uniqueness of the new record(s)
518
- amoeba_conf.customizations.each do |block|
519
- block.call(self, @result)
520
- end
521
- end
522
- # }}}
523
- end
524
15
  end
525
16
 
526
- ActiveRecord::Base.send :extend, Amoeba::ClassMethods
17
+ ActiveRecord::Base.send :extend, Amoeba::ClassMethods
527
18
  ActiveRecord::Base.send :include, Amoeba::InstanceMethods