agnostic-duplicate 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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +2 -0
- data/agnostic-duplicate.gemspec +34 -0
- data/lib/agnostic/duplicate/version.rb +5 -0
- data/lib/agnostic/duplicate.rb +262 -0
- data/spec/agnostic_duplicate_spec.rb +249 -0
- data/spec/spec_helper.rb +4 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 32cdc91880b4712356cc059c8460a991cd79d39e
|
4
|
+
data.tar.gz: 79d5fb7a6d55d51ece7ee459b5fd95fdb164cacd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8dab11d2925bb2e145230cd2ff3cb4e341f4cfaf68a7bca55d11805d712f257979a8d0d19b61e975ec13cfffb28e47293f9a0b614bce5aed13c0bcd9a2b52e7c
|
7
|
+
data.tar.gz: 74020d056b6f1493a9788281476fa52b4e743a1aa9466be38137e8522b4cda172eac53fc67973de2a3177f716bd0a1893d3d0dee96676763cd2fbaa49af36c94
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
agnostic-duplicate
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.2
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 David Saenz Tagarro
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Agnostic::Duplicate
|
2
|
+
|
3
|
+
Duplicate objects are provided with an additional method `duplicate` that
|
4
|
+
extends the method `dup` functionality, allowing deep copy or shallow copy of
|
5
|
+
specific fields.
|
6
|
+
|
7
|
+
## When to use
|
8
|
+
|
9
|
+
The advantage of using Duplicate module reside in support for fields that
|
10
|
+
are not duplicated by default for any reason by calling `dup`. Example: when
|
11
|
+
using Rails `dup` implementation doesn't copy attributes of model that return an
|
12
|
+
ActiveRecord::Relation, it is supossed the developer to choose his strategy.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
gem 'agnostic-duplicate'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install agnostic-duplicate
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
When using `Duplicate` you specify a list of attributes that you want to be
|
31
|
+
copied additionaly to the object returned by `dup`. Though if `dup` returns
|
32
|
+
a value for an attribute and you mark that attribute as "duplicable" then
|
33
|
+
the value of the attribute will be overwritten with the value provided by
|
34
|
+
`duplicate` call.
|
35
|
+
|
36
|
+
Example:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class Story < ActiveRecord::Base
|
40
|
+
include Duplicate
|
41
|
+
# ...
|
42
|
+
attr_duplicable :seo_element, :category, :properties
|
43
|
+
# ...
|
44
|
+
attr_accessible :title
|
45
|
+
# ...
|
46
|
+
has_one :seo_element, as: :metadatable
|
47
|
+
has_one :category, through: :categorisation, source: :category
|
48
|
+
has_many :properties, :images, :headlines
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
When using `duplicable` over any attribute, it verifies if the current value
|
54
|
+
value implements `Duplicate`. In that case it returns the result of calling
|
55
|
+
to `duplicate` on that object. If the attribute doesn't implement
|
56
|
+
`Duplicate` it is returned the `dup` value.
|
57
|
+
|
58
|
+
If the `duplicable` attribute is iterable then it is returned an array where
|
59
|
+
every element of the collection is duplicated following the flow defined
|
60
|
+
previously.
|
61
|
+
|
62
|
+
Also it is possible to provide **shallow copies** of attribute values,
|
63
|
+
modifying the default behaviour. In that case, just make use of the
|
64
|
+
`strategy` option.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
attr_duplicable :images, strategy: :shallow_copy
|
68
|
+
```
|
69
|
+
|
70
|
+
It is given support for custom behaviour after duplication process. In that
|
71
|
+
case it is only required to implement the method `hook_after_duplicate!`
|
72
|
+
|
73
|
+
Extending previous example:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
def hook_after_duplicate!(duplicate)
|
77
|
+
duplicate.headlines = self.headlines.not_orphans.collect(&:dup)
|
78
|
+
duplicate.images.each { |img| img.attachable = duplicate }
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
**ATENTION:** Observe that `model` passed as parameter is in fact the
|
83
|
+
duplicated instance that it is going to be returned
|
84
|
+
|
85
|
+
## Configuration options
|
86
|
+
|
87
|
+
If the only attribute values you want to be duplicated are the ones you have
|
88
|
+
specified through the `attr_duplicable` method, and though removing the
|
89
|
+
additional fields duplicated because of the init call to `dup`, then you can
|
90
|
+
set this configuration through `duplicable_config` method:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class Image < ActiveRecord::Base
|
94
|
+
include Duplicate
|
95
|
+
duplicable_config new_instance: true
|
96
|
+
# ...
|
97
|
+
attr_duplicable :images
|
98
|
+
# ...
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
## Contributing
|
103
|
+
|
104
|
+
1. Fork it ( https://github.com/[my-github-username]/agnostic-duplicate/fork )
|
105
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
106
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
107
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
108
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'agnostic/duplicate/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'agnostic-duplicate'
|
8
|
+
spec.version = Agnostic::Duplicate::VERSION
|
9
|
+
spec.authors = ['David Saenz Tagarro']
|
10
|
+
spec.email = ['david.saenz.tagarro@gmail.com']
|
11
|
+
spec.summary = <<-summary
|
12
|
+
Duplicate library provides additional support for deep copy or shallow copy of
|
13
|
+
specific fields in your models while you are `dupping` an instance.)
|
14
|
+
summary
|
15
|
+
spec.description = <<-description
|
16
|
+
The advantage of using Duplicate module reside in support for fields that
|
17
|
+
are not duplicated by default for any reason by calling `dup`.)
|
18
|
+
description
|
19
|
+
spec.homepage = 'https://github.com/dsaenztagarro/agnostic-duplicate'
|
20
|
+
spec.license = 'MIT'
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0")
|
23
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
25
|
+
spec.require_paths = ['lib']
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'simplecov'
|
31
|
+
spec.add_development_dependency 'rubocop'
|
32
|
+
spec.add_development_dependency 'reek'
|
33
|
+
spec.add_development_dependency 'cane'
|
34
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require 'agnostic/duplicate/version'
|
2
|
+
|
3
|
+
module Agnostic
|
4
|
+
# Duplicate objects are provided with an additional method `duplicate` that
|
5
|
+
# extends the method `dup` functionality.
|
6
|
+
#
|
7
|
+
# ## When to use
|
8
|
+
#
|
9
|
+
# The advantage of using Duplicate module reside in support for fields that
|
10
|
+
# are not duplicated by default for any reason. Example: when using Rails
|
11
|
+
# `dup` implementation doesn't copy attributes of model that return an
|
12
|
+
# ActiveRecord::Relation, it is supossed the developer to choose his strategy.
|
13
|
+
#
|
14
|
+
# ## Usage
|
15
|
+
#
|
16
|
+
# When using `Duplicate` you specify a list of attributes that you want to be
|
17
|
+
# copied additionaly to the object returned by `dup`. Though if `dup` returns
|
18
|
+
# a value for an attribute and you mark that attribute as "duplicable" then
|
19
|
+
# the value of the attribute will be overwritten with the value provided by
|
20
|
+
# `duplicate` call.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# ```ruby
|
25
|
+
# class Story < ActiveRecord::Base
|
26
|
+
# include Duplicate
|
27
|
+
# # ...
|
28
|
+
# attr_duplicable :seo_element, :category, :properties
|
29
|
+
# # ...
|
30
|
+
# attr_accessible :title
|
31
|
+
# # ...
|
32
|
+
# has_one :seo_element, as: :metadatable
|
33
|
+
# has_one :category, through: :categorisation, source: :category
|
34
|
+
# has_many :properties, :images, :headlines
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
# ```
|
38
|
+
#
|
39
|
+
# When using `duplicable` over any attribute, it verifies if the current value
|
40
|
+
# value implements `Duplicate`. In that case it returns the result of calling
|
41
|
+
# to `duplicate` on that object. If the attribute doesn't implement
|
42
|
+
# `Duplicate` it is returned the `dup` value.
|
43
|
+
#
|
44
|
+
# If the `duplicable` attribute is iterable then it is returned an array where
|
45
|
+
# every element of the collection is duplicated following the flow defined
|
46
|
+
# previously.
|
47
|
+
#
|
48
|
+
# Also it is possible to provide **shallow copies** of attribute values,
|
49
|
+
# modifying the default behaviour. In that case, just make use of the
|
50
|
+
# `strategy` option.
|
51
|
+
#
|
52
|
+
# ```ruby
|
53
|
+
# attr_duplicable :images, strategy: :shallow_copy
|
54
|
+
# ```
|
55
|
+
#
|
56
|
+
# It is given support for custom behaviour after duplication process. In that
|
57
|
+
# case it is only required to implement the method `hook_after_duplicate!`
|
58
|
+
#
|
59
|
+
# Extending previous example:
|
60
|
+
#
|
61
|
+
# ```ruby
|
62
|
+
# def hook_after_duplicate!(duplicate)
|
63
|
+
# duplicate.headlines = self.headlines.not_orphans.collect(&:dup)
|
64
|
+
# duplicate.images.each { |img| img.attachable = duplicate }
|
65
|
+
# end
|
66
|
+
# ```
|
67
|
+
#
|
68
|
+
# **ATENTION:** Observe that `model` passed as parameter is in fact the
|
69
|
+
# duplicated instance that it is going to be returned
|
70
|
+
#
|
71
|
+
# ## Configuration options
|
72
|
+
#
|
73
|
+
# If the only attribute values you want to be duplicated are the ones you have
|
74
|
+
# specified through the `attr_duplicable` method, and though removing the
|
75
|
+
# additional fields duplicated because of the init call to `dup`, then you can
|
76
|
+
# set this configuration through `duplicable_config` method:
|
77
|
+
#
|
78
|
+
# ```ruby
|
79
|
+
# class Image < ActiveRecord::Base
|
80
|
+
# include Duplicate
|
81
|
+
# duplicable_config new_instance: true
|
82
|
+
# # ...
|
83
|
+
# attr_duplicable :images
|
84
|
+
# # ...
|
85
|
+
# end
|
86
|
+
# ```
|
87
|
+
module Duplicate
|
88
|
+
def self.included(base)
|
89
|
+
base.extend(ClassMethods)
|
90
|
+
base.instance_variable_set '@duplicable_changesets', []
|
91
|
+
base.instance_variable_set '@duplicable_options', {}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Duplicates the object
|
95
|
+
# @return [Duplicate] the new instance object
|
96
|
+
def duplicate
|
97
|
+
dup_template.tap do |model|
|
98
|
+
apply_changesets!(model)
|
99
|
+
hook_after_duplicate!(model) if respond_to? :hook_after_duplicate!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Applies to model the duplicable changesets defined in class definition
|
106
|
+
# @param model [Duplicate] the duplicated new instance object
|
107
|
+
def apply_changesets!(model)
|
108
|
+
self.class.duplicable_changesets.each do |changeset|
|
109
|
+
changeset.apply(self, model)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Contains all kinds of changesets that can be applied to a duplicable
|
114
|
+
# object
|
115
|
+
module ChangeSet
|
116
|
+
# Base class for all changesets. Subclasses should implement method
|
117
|
+
# `apply` (see #apply)
|
118
|
+
class Base
|
119
|
+
attr_reader :attributes
|
120
|
+
def initialize(attributes)
|
121
|
+
@attributes = attributes
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Defines a changeset where a deep copy wants to be applied to all
|
126
|
+
# attributes
|
127
|
+
class DeepCopy < Base
|
128
|
+
# Applies changes needed on the duplicated new instance object
|
129
|
+
# @param parent [Duplicate] the original object to be duplicated
|
130
|
+
# @param model [Duplicate] the duplicated new instance object
|
131
|
+
def apply(parent, model)
|
132
|
+
attributes.each do |attribute|
|
133
|
+
setter_method = "#{attribute}="
|
134
|
+
if model.respond_to?(setter_method)
|
135
|
+
model.send(setter_method, dup_attribute(parent, attribute))
|
136
|
+
else
|
137
|
+
fail "Invalid duplicable attribute '#{attribute}'"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# @param parent [Duplicate] the original object to be duplicated
|
145
|
+
# @param attribute [Symbol] the attribute to be duplicated
|
146
|
+
# @return from a duplicable object the duplicated value for the
|
147
|
+
# attribute specified
|
148
|
+
def dup_attribute(parent, attribute)
|
149
|
+
value = parent.send(attribute)
|
150
|
+
klass = self.class
|
151
|
+
if value && value.respond_to?(:collect)
|
152
|
+
value.map { |item| klass.dup_item(item) }
|
153
|
+
else
|
154
|
+
value && klass.dup_item(value)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Duplicates the object passed as parameter
|
159
|
+
# @param item [Object] object to be duplicated
|
160
|
+
# @return [Object] the duplicated new instance object
|
161
|
+
def self.dup_item(item)
|
162
|
+
if item.respond_to? :duplicate
|
163
|
+
item.duplicate
|
164
|
+
else
|
165
|
+
item.dup
|
166
|
+
end
|
167
|
+
rescue
|
168
|
+
item
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Defines a changeset where a deep copy wants to be applied to all
|
173
|
+
# attributes.
|
174
|
+
#
|
175
|
+
# Though if the field value is a memory address it copies the memory
|
176
|
+
# address, and if the field value is a primitive type it copies the value
|
177
|
+
# of the primitive type.
|
178
|
+
class ShallowCopy < Base
|
179
|
+
# Applies changes needed on the duplicated new instance object
|
180
|
+
# @param parent [Duplicate] the original object to be duplicated
|
181
|
+
# @param model [Duplicate] the duplicated new instance object
|
182
|
+
def apply(parent, model)
|
183
|
+
attributes.each do |attribute|
|
184
|
+
model.send("#{attribute}=", parent.send(attribute))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
# @return [Duplicate] a new instance object based on global duplicable
|
193
|
+
# configuration
|
194
|
+
def dup_template
|
195
|
+
klass = self.class
|
196
|
+
if klass.duplicable_option? :new_instance
|
197
|
+
klass.new
|
198
|
+
else
|
199
|
+
dup
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Methods added to classes including Duplicate module
|
204
|
+
module ClassMethods
|
205
|
+
attr_accessor :duplicable_changesets, :duplicable_options
|
206
|
+
|
207
|
+
# Adds a new duplicable changeset for the class.
|
208
|
+
#
|
209
|
+
# By default created changesets apply a deep copy strategy over the
|
210
|
+
# attributes specified. If you want to set a shallow copy strategy then
|
211
|
+
# you can add the option `strategy: :shallow_copy`
|
212
|
+
#
|
213
|
+
# @param *args [Array<Symbol>] a list of attribute names
|
214
|
+
# @param options [Hash] options specific for the changeset
|
215
|
+
def attr_duplicable(*args)
|
216
|
+
@changeset_options = {}
|
217
|
+
@changeset_options = args.pop if args.last.is_a? Hash
|
218
|
+
duplicable_changesets << changeset_class.new(args)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Sets global options for applying changesets
|
222
|
+
#
|
223
|
+
# ## Options available:
|
224
|
+
# - `new_instance`: if `true` the duplicated instance is created calling
|
225
|
+
# in first place `new` method over the class. if `false` the duplicated
|
226
|
+
# instance is created calling to `dup` method over the instance object.
|
227
|
+
#
|
228
|
+
# @param options [Hash]
|
229
|
+
def duplicable_config(options)
|
230
|
+
if options.is_a? Hash
|
231
|
+
@duplicable_options.merge! options
|
232
|
+
keep_valid_options
|
233
|
+
else
|
234
|
+
fail ArgumentError, 'Invalid options configuration'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# @param option [Symbol] global option for duplication
|
239
|
+
# @return [Boolean] the boolean value expressing if the option is
|
240
|
+
# activated
|
241
|
+
def duplicable_option?(option)
|
242
|
+
@duplicable_options ||= {}
|
243
|
+
@duplicable_options[option]
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# Remove unknown options for applying changesets
|
249
|
+
def keep_valid_options
|
250
|
+
@duplicable_options.keep_if { |key, _| [:new_instance].include? key }
|
251
|
+
end
|
252
|
+
|
253
|
+
# @return [ChangeSet::Object] based on the strategy of duplication to be
|
254
|
+
# applied over the attributes
|
255
|
+
def changeset_class
|
256
|
+
strategy = @changeset_options[:strategy] || :deep_copy
|
257
|
+
class_name = strategy.to_s.split('_').map(&:capitalize).join
|
258
|
+
Duplicate.const_get('ChangeSet').const_get("#{class_name}")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
#:nodoc
|
4
|
+
module DuplicateSpec
|
5
|
+
NO_DUP_VALUE = 1999
|
6
|
+
DUP_VALUE = 'testValue'
|
7
|
+
|
8
|
+
#:nodoc:
|
9
|
+
class Base
|
10
|
+
attr_accessor :id, :name, :images
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@id = options[:id]
|
14
|
+
@name = options[:name]
|
15
|
+
@images = options[:images]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#:nodoc
|
20
|
+
# Dummy class duplicated through relationship. Not implementing Duplicable.
|
21
|
+
class Image
|
22
|
+
attr_accessor :id, :caption
|
23
|
+
def initialize(options = {})
|
24
|
+
@id = options[:id]
|
25
|
+
@caption = options[:caption]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#:nodoc
|
30
|
+
# Dummy class duplicated through relationship. Implementing Duplicable.
|
31
|
+
class ImageDuplicable < Image
|
32
|
+
include Agnostic::Duplicate
|
33
|
+
attr_duplicable :caption
|
34
|
+
end
|
35
|
+
|
36
|
+
# Factory for dummy image lists
|
37
|
+
def self.create_list(class_name, size)
|
38
|
+
const_name = class_name.to_s.split('_').map(&:capitalize).join
|
39
|
+
klass = DuplicateSpec.const_get(const_name)
|
40
|
+
[].tap do |list|
|
41
|
+
size.times do |n|
|
42
|
+
instance = klass.new(id: n, caption: "Cap #{n}")
|
43
|
+
instance.stub(:dup).and_return(klass.new(id: n))
|
44
|
+
list << instance
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
RSpec.shared_context 'iterable field not dup' do
|
51
|
+
before(:each) do
|
52
|
+
@images = DuplicateSpec.create_list image_type, 3
|
53
|
+
@dummy = DuplicateSpec::DeepCopy.new(id: 1, name: 'Dummy', images: @images)
|
54
|
+
@dummy.stub(:dup).and_return(
|
55
|
+
DuplicateSpec::DeepCopy.new(id: 1), name: 'Dummy'
|
56
|
+
)
|
57
|
+
@duplicate = @dummy.duplicate
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
RSpec.shared_context 'not iterable field not dup' do
|
62
|
+
before(:each) do
|
63
|
+
@dummy = DuplicateSpec::DeepCopy.new(id: 1, name: attribute_value)
|
64
|
+
@dummy.stub(:dup).and_return(DuplicateSpec::DeepCopy.new(id: 1))
|
65
|
+
@duplicate = @dummy.duplicate
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe Agnostic::Duplicate do
|
70
|
+
|
71
|
+
subject { @dummy }
|
72
|
+
|
73
|
+
describe '#duplicate' do
|
74
|
+
it { respond_to :duplicate }
|
75
|
+
it { respond_to :duplicable_config }
|
76
|
+
it { respond_to :duplicate_as_new_instance? }
|
77
|
+
it { respond_to :attr_duplicable }
|
78
|
+
|
79
|
+
context 'when applying changesets' do
|
80
|
+
context 'when deep copy strategy' do
|
81
|
+
before(:all) do
|
82
|
+
module DuplicateSpec
|
83
|
+
#:nodoc
|
84
|
+
class DeepCopy < Base
|
85
|
+
include Agnostic::Duplicate
|
86
|
+
attr_duplicable :name, :images
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
context 'when attribute is iterable' do
|
91
|
+
context 'when elements implement Duplicable' do
|
92
|
+
let(:image_type) { :image_duplicable }
|
93
|
+
include_context 'iterable field not dup'
|
94
|
+
|
95
|
+
it 'is duplicated the collection' do
|
96
|
+
expect(@duplicate.images.size).to be(@images.size)
|
97
|
+
end
|
98
|
+
it 'copies duplicable attributes' do
|
99
|
+
@duplicate.images.zip(@dummy.images).each do |copy, parent|
|
100
|
+
expect(copy.caption).to eq(parent.caption)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
context "when elements don't implement Duplicable" do
|
105
|
+
let(:image_type) { :image }
|
106
|
+
include_context 'iterable field not dup'
|
107
|
+
|
108
|
+
it 'is duplicated the collection' do
|
109
|
+
expect(@duplicate.images.size).to be(@images.size)
|
110
|
+
end
|
111
|
+
it "doesn't copy no duplicable attributes" do
|
112
|
+
@duplicate.images.each do |image|
|
113
|
+
expect(image.caption).to be(nil)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
context 'when attribute is not iterable' do
|
119
|
+
context "when it cann't be dupped" do
|
120
|
+
let(:attribute_value) { DuplicateSpec::NO_DUP_VALUE }
|
121
|
+
include_context 'not iterable field not dup'
|
122
|
+
|
123
|
+
it 'is duplicated' do
|
124
|
+
expect(@duplicate.name).to eq(DuplicateSpec::NO_DUP_VALUE)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
context 'when it can be dupped' do
|
128
|
+
let(:attribute_value) { DuplicateSpec::DUP_VALUE }
|
129
|
+
include_context 'not iterable field not dup'
|
130
|
+
|
131
|
+
it 'is duplicated' do
|
132
|
+
expect(@duplicate.name).to eq(DuplicateSpec::DUP_VALUE)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
context 'when shallow copy strategy' do
|
138
|
+
before :all do
|
139
|
+
module DuplicateSpec
|
140
|
+
#:nodoc
|
141
|
+
class ShallowCopy < Base
|
142
|
+
include Agnostic::Duplicate
|
143
|
+
attr_duplicable :images, strategy: :shallow_copy
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
before(:each) do
|
149
|
+
@images = DuplicateSpec.create_list :image_duplicable, 3
|
150
|
+
@dummy = DuplicateSpec::ShallowCopy.new(
|
151
|
+
id: 1,
|
152
|
+
name: DuplicateSpec::DUP_VALUE,
|
153
|
+
images: @images)
|
154
|
+
@duplicate = @dummy.duplicate
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'copies values keeping the object reference' do
|
158
|
+
expect(@duplicate.images).to be(@images)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
context 'when applying hook after duplicate' do
|
163
|
+
before(:all) do
|
164
|
+
module DuplicateSpec
|
165
|
+
#:nodoc
|
166
|
+
class DeepCopyWithHook < Base
|
167
|
+
include Agnostic::Duplicate
|
168
|
+
attr_duplicable :name, :images
|
169
|
+
|
170
|
+
def hook_after_duplicate!(duplicate)
|
171
|
+
duplicate.name = "#{duplicate.name} hooked"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
before(:each) do
|
177
|
+
@name = DuplicateSpec::DUP_VALUE
|
178
|
+
@dummy = DuplicateSpec::DeepCopyWithHook.new(id: 1, name: @name)
|
179
|
+
@dummy.stub(:dup).and_return(
|
180
|
+
DuplicateSpec::DeepCopyWithHook.new(id: 1))
|
181
|
+
@duplicate = @dummy.duplicate
|
182
|
+
end
|
183
|
+
it 'overrides changes made during duplication' do
|
184
|
+
expect(@duplicate.name).to eq("#{@name} hooked")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
context 'when duplicating with new instance option' do
|
188
|
+
before :all do
|
189
|
+
module DuplicateSpec
|
190
|
+
#:nodoc
|
191
|
+
class NewInstanceTest < Base
|
192
|
+
include Agnostic::Duplicate
|
193
|
+
duplicable_config new_instance: true
|
194
|
+
attr_duplicable :images
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
before(:each) do
|
200
|
+
@images = DuplicateSpec.create_list :image_duplicable, 3
|
201
|
+
@dummy = DuplicateSpec::NewInstanceTest.new(
|
202
|
+
id: 1,
|
203
|
+
name: DuplicateSpec::DUP_VALUE,
|
204
|
+
images: @images)
|
205
|
+
@duplicate = @dummy.duplicate
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'duplicates only fields for #attr_duplicable' do
|
209
|
+
expect(@duplicate.id).to be(nil)
|
210
|
+
expect(@duplicate.name).to be(nil)
|
211
|
+
expect(@duplicate.images.size).to be(@images.size)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
context 'when invalid settings' do
|
215
|
+
context "when attribute doesn't exist" do
|
216
|
+
it 'raises an exception' do
|
217
|
+
expect do
|
218
|
+
#:nodoc
|
219
|
+
module DuplicateSpec
|
220
|
+
#:nodoc
|
221
|
+
class InvalidAttributeTest
|
222
|
+
include Agnostic::Duplicate
|
223
|
+
attr_duplicable :name
|
224
|
+
end
|
225
|
+
InvalidAttributeTest.new.duplicate
|
226
|
+
end
|
227
|
+
end.to raise_error("Invalid duplicable attribute 'name'")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
context 'when duplicable config is invalid' do
|
231
|
+
it 'raises an exception' do
|
232
|
+
expect do
|
233
|
+
#:nodoc
|
234
|
+
module DuplicateSpec
|
235
|
+
#:nodoc
|
236
|
+
class DummyInvalidConfig
|
237
|
+
include Agnostic::Duplicate
|
238
|
+
duplicable_config :new_instance
|
239
|
+
attr_duplicable :name
|
240
|
+
end
|
241
|
+
|
242
|
+
DummyInvalidConfig.new.duplicate
|
243
|
+
end
|
244
|
+
end.to raise_error('Invalid options configuration')
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: agnostic-duplicate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Saenz Tagarro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: reek
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: cane
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: |
|
112
|
+
The advantage of using Duplicate module reside in support for fields that
|
113
|
+
are not duplicated by default for any reason by calling `dup`.)
|
114
|
+
email:
|
115
|
+
- david.saenz.tagarro@gmail.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- ".gitignore"
|
121
|
+
- ".rspec"
|
122
|
+
- ".ruby-gemset"
|
123
|
+
- ".ruby-version"
|
124
|
+
- ".travis.yml"
|
125
|
+
- Gemfile
|
126
|
+
- LICENSE.txt
|
127
|
+
- README.md
|
128
|
+
- Rakefile
|
129
|
+
- agnostic-duplicate.gemspec
|
130
|
+
- lib/agnostic/duplicate.rb
|
131
|
+
- lib/agnostic/duplicate/version.rb
|
132
|
+
- spec/agnostic_duplicate_spec.rb
|
133
|
+
- spec/spec_helper.rb
|
134
|
+
homepage: https://github.com/dsaenztagarro/agnostic-duplicate
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.2.2
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: Duplicate library provides additional support for deep copy or shallow copy
|
158
|
+
of specific fields in your models while you are `dupping` an instance.)
|
159
|
+
test_files:
|
160
|
+
- spec/agnostic_duplicate_spec.rb
|
161
|
+
- spec/spec_helper.rb
|