clowne 0.1.0.pre1 → 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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +15 -2
- data/CHANGELOG.md +9 -2
- data/Gemfile +1 -0
- data/README.md +25 -381
- data/clowne.gemspec +1 -0
- data/docs/.rubocop.yml +12 -0
- data/docs/active_record.md +36 -0
- data/docs/alternatives.md +26 -0
- data/docs/architecture.md +141 -0
- data/docs/basic_example.md +66 -0
- data/docs/configuration.md +29 -0
- data/docs/customization.md +64 -0
- data/docs/exclude_association.md +63 -0
- data/docs/execution_order.md +14 -0
- data/docs/finalize.md +35 -0
- data/docs/implicit_cloner.md +36 -0
- data/docs/include_association.md +119 -0
- data/docs/init_as.md +36 -0
- data/docs/inline_configuration.md +38 -0
- data/docs/installation.md +16 -0
- data/docs/nullify.md +37 -0
- data/docs/sequel.md +56 -0
- data/docs/supported_adapters.md +13 -0
- data/docs/traits.md +28 -0
- data/docs/web/.gitignore +11 -0
- data/docs/web/core/Footer.js +92 -0
- data/docs/web/i18n/en.json +134 -0
- data/docs/web/package.json +14 -0
- data/docs/web/pages/en/help.js +50 -0
- data/docs/web/pages/en/index.js +231 -0
- data/docs/web/pages/en/users.js +47 -0
- data/docs/web/sidebars.json +30 -0
- data/docs/web/siteConfig.js +44 -0
- data/docs/web/static/css/custom.css +229 -0
- data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
- data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
- data/docs/web/static/fonts/StemText.woff +0 -0
- data/docs/web/static/fonts/StemTextBold.woff +0 -0
- data/docs/web/static/img/favicon/favicon.ico +0 -0
- data/docs/web/yarn.lock +1741 -0
- data/gemfiles/activerecord42.gemfile +1 -0
- data/gemfiles/jruby.gemfile +2 -0
- data/gemfiles/railsmaster.gemfile +1 -0
- data/lib/clowne.rb +3 -1
- data/lib/clowne/adapters/active_record.rb +3 -12
- data/lib/clowne/adapters/active_record/association.rb +1 -1
- data/lib/clowne/adapters/active_record/associations/base.rb +8 -48
- data/lib/clowne/adapters/active_record/associations/has_one.rb +8 -1
- data/lib/clowne/adapters/active_record/associations/noop.rb +4 -1
- data/lib/clowne/adapters/active_record/dsl.rb +33 -0
- data/lib/clowne/adapters/base.rb +13 -6
- data/lib/clowne/adapters/base/association.rb +69 -0
- data/lib/clowne/adapters/base/finalize.rb +1 -1
- data/lib/clowne/adapters/base/init_as.rb +21 -0
- data/lib/clowne/adapters/registry.rb +5 -11
- data/lib/clowne/adapters/sequel.rb +25 -0
- data/lib/clowne/adapters/sequel/association.rb +47 -0
- data/lib/clowne/adapters/sequel/associations.rb +26 -0
- data/lib/clowne/adapters/sequel/associations/base.rb +23 -0
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +19 -0
- data/lib/clowne/adapters/sequel/associations/noop.rb +16 -0
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +23 -0
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +23 -0
- data/lib/clowne/adapters/sequel/copier.rb +23 -0
- data/lib/clowne/adapters/sequel/record_wrapper.rb +59 -0
- data/lib/clowne/cloner.rb +6 -4
- data/lib/clowne/declarations.rb +1 -0
- data/lib/clowne/declarations/exclude_association.rb +0 -5
- data/lib/clowne/declarations/include_association.rb +0 -2
- data/lib/clowne/declarations/init_as.rb +20 -0
- data/lib/clowne/declarations/trait.rb +2 -0
- data/lib/clowne/ext/orm_ext.rb +21 -0
- data/lib/clowne/ext/string_constantize.rb +2 -2
- data/lib/clowne/planner.rb +11 -4
- data/lib/clowne/version.rb +1 -1
- metadata +70 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb07a988be474d61f3bbf9360cfc4ab25de047be
|
4
|
+
data.tar.gz: 1a4763ae4bd1537ab0a254d4a0ebcb5ddd56930d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ff3270acfabe0aebebfd272b3cb6eb27c9c7faea337fb59489898e008095d71dbfb835062e6c99c3b0fbc4b63e909073dd7e9e46231a6c3a0ecf5762f7204ef
|
7
|
+
data.tar.gz: 53e267bcb71f9283a64bb95df753e26b38058d5283c0b0b01276209fe6a27a07b3906cfbae881734875fca9a987e25fcbda6b3143f44956ef361102296821860
|
data/.codeclimate.yml
ADDED
data/.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
docs/**/* linguist-vendored
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,19 +1,29 @@
|
|
1
|
+
require:
|
2
|
+
- 'rubocop-md'
|
1
3
|
AllCops:
|
2
4
|
Exclude:
|
3
5
|
- 'bin/**/*'
|
4
6
|
- 'tmp/**/*'
|
7
|
+
- 'docs/web/**/*'
|
5
8
|
- 'vendor/**/*'
|
6
9
|
- 'gemfiles/vendor/**/*'
|
7
10
|
DisplayCopNames: true
|
8
11
|
StyleGuideCopsOnly: false
|
9
12
|
TargetRubyVersion: 2.3
|
10
13
|
|
14
|
+
Markdown:
|
15
|
+
WarnInvalid: true
|
16
|
+
|
11
17
|
Rails:
|
12
18
|
Enabled: false
|
13
19
|
|
14
20
|
Naming/AccessorMethodName:
|
15
21
|
Enabled: false
|
16
22
|
|
23
|
+
Naming/ClassAndModuleCamelCase:
|
24
|
+
Exclude:
|
25
|
+
- 'spec/**/*.rb'
|
26
|
+
|
17
27
|
Style/TrivialAccessors:
|
18
28
|
Enabled: false
|
19
29
|
|
@@ -23,6 +33,7 @@ Metrics/LineLength:
|
|
23
33
|
Style/Documentation:
|
24
34
|
Exclude:
|
25
35
|
- 'spec/**/*.rb'
|
36
|
+
- 'README.md'
|
26
37
|
|
27
38
|
Style/SymbolArray:
|
28
39
|
Enabled: false
|
@@ -33,6 +44,8 @@ Style/FrozenStringLiteralComment:
|
|
33
44
|
- 'Gemfile'
|
34
45
|
- 'Rakefile'
|
35
46
|
- '*.gemspec'
|
47
|
+
- 'CHANGELOG.md'
|
48
|
+
- 'README.md'
|
36
49
|
|
37
50
|
Metrics/BlockLength:
|
38
51
|
Exclude:
|
@@ -43,3 +56,7 @@ Bundler/OrderedGems:
|
|
43
56
|
|
44
57
|
Gemspec/OrderedDependencies:
|
45
58
|
Enabled: false
|
59
|
+
|
60
|
+
Lint/Void:
|
61
|
+
Exclude:
|
62
|
+
- 'README.md'
|
data/.travis.yml
CHANGED
@@ -4,6 +4,9 @@ language: ruby
|
|
4
4
|
notifications:
|
5
5
|
email: false
|
6
6
|
|
7
|
+
before_install:
|
8
|
+
gem update --system
|
9
|
+
|
7
10
|
before_script:
|
8
11
|
# Only generate coverage report for the specified job
|
9
12
|
- if [ "$CC_REPORT" == "true" ]; then curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter; fi
|
@@ -11,8 +14,8 @@ before_script:
|
|
11
14
|
- if [ "$CC_REPORT" == "true" ]; then ./cc-test-reporter before-build; fi
|
12
15
|
script:
|
13
16
|
- bundle exec rake
|
14
|
-
after_script:
|
15
17
|
- if [ "$CC_REPORT" == "true" ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi
|
18
|
+
- if [ "$DEPLOY_ME" == "true" ]; then (cd ./docs/web && yarn && yarn run build); fi
|
16
19
|
|
17
20
|
matrix:
|
18
21
|
fast_finish: true
|
@@ -25,7 +28,9 @@ matrix:
|
|
25
28
|
gemfile: Gemfile
|
26
29
|
- rvm: 2.4.3
|
27
30
|
gemfile: Gemfile
|
28
|
-
env:
|
31
|
+
env:
|
32
|
+
- CC_REPORT=true
|
33
|
+
- DEPLOY_ME=true
|
29
34
|
- rvm: 2.4.1
|
30
35
|
gemfile: gemfiles/activerecord42.gemfile
|
31
36
|
- rvm: 2.3.1
|
@@ -37,3 +42,11 @@ matrix:
|
|
37
42
|
gemfile: gemfiles/railsmaster.gemfile
|
38
43
|
- rvm: jruby-9.1.0.0
|
39
44
|
gemfile: gemfiles/jruby.gemfile
|
45
|
+
deploy:
|
46
|
+
provider: pages
|
47
|
+
skip_cleanup: true
|
48
|
+
github_token: $GITHUB_TOKEN
|
49
|
+
local_dir: "./docs/web/build/clowne"
|
50
|
+
on:
|
51
|
+
branch: master
|
52
|
+
condition: $DEPLOY_ME = true
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
-
##
|
3
|
+
## 0.1.0 (2018-02-01)
|
4
|
+
|
5
|
+
- Add `init_as` declaration. ([@palkan][])
|
6
|
+
|
7
|
+
- Support [Sequel](https://github.com/jeremyevans/sequel). ([@ssnickolay][])
|
8
|
+
|
9
|
+
- Support passing a block to `#clowne` for inline configuration. ([@palkan][])
|
10
|
+
|
11
|
+
## 0.1.0.beta1 (2018-01-08)
|
4
12
|
|
5
13
|
- Initial version. ([@ssnickolay][], [@palkan][])
|
6
14
|
|
7
15
|
[@palkan]: https://github.com/palkan
|
8
16
|
[@ssnickolay]: https://github.com/ssnickolay
|
9
|
-
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,26 +1,17 @@
|
|
1
1
|
[](https://badge.fury.io/rb/clowne)
|
2
2
|
[](https://travis-ci.org/palkan/clowne)
|
3
|
-
[](https://codeclimate.com/github/palkan/clowne)
|
4
3
|
[](https://codeclimate.com/github/palkan/clowne/coverage)
|
4
|
+
[](https://palkan.github.io/clowne)
|
5
5
|
|
6
6
|
# Clowne
|
7
7
|
|
8
|
-
**
|
8
|
+
**NOTE**: this is the documentation for pre-release version **0.1.0.beta1**.
|
9
9
|
|
10
|
-
A flexible gem for cloning your models. Clowne focuses on ease of use and provides the ability to connect various ORM adapters
|
10
|
+
A flexible gem for cloning your models. Clowne focuses on ease of use and provides the ability to connect various ORM adapters.
|
11
11
|
|
12
12
|
<a href="https://evilmartians.com/">
|
13
13
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
14
14
|
|
15
|
-
### Alternatives
|
16
|
-
|
17
|
-
Why did we decide to build our own cloning gem?
|
18
|
-
|
19
|
-
First, existing solutions turned out not stable and flexible enough for us.
|
20
|
-
|
21
|
-
Secondly, they are Rails-only. And we are not.
|
22
|
-
|
23
|
-
Nevertheless, thanks to [amoeba](https://github.com/amoeba-rb/amoeba) and [deep_cloneable](https://github.com/moiristo/deep_cloneable) for inspiration.
|
24
15
|
|
25
16
|
## Installation
|
26
17
|
|
@@ -38,9 +29,7 @@ gem 'clowne'
|
|
38
29
|
|
39
30
|
## Quick Start
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
At first, define your cloneable model
|
32
|
+
Assume that you have the following model:
|
44
33
|
|
45
34
|
```ruby
|
46
35
|
class User < ActiveRecord::Base
|
@@ -55,7 +44,7 @@ class User < ActiveRecord::Base
|
|
55
44
|
end
|
56
45
|
```
|
57
46
|
|
58
|
-
|
47
|
+
Let's declare our cloners first:
|
59
48
|
|
60
49
|
```ruby
|
61
50
|
class UserCloner < Clowne::Cloner
|
@@ -65,8 +54,8 @@ class UserCloner < Clowne::Cloner
|
|
65
54
|
include_association :posts
|
66
55
|
|
67
56
|
nullify :login
|
68
|
-
|
69
|
-
# params here is an arbitrary
|
57
|
+
|
58
|
+
# params here is an arbitrary Hash passed into cloner
|
70
59
|
finalize do |_source, record, params|
|
71
60
|
record.email = params[:email]
|
72
61
|
end
|
@@ -79,382 +68,37 @@ class SpecialProfileCloner < Clowne::Cloner
|
|
79
68
|
end
|
80
69
|
```
|
81
70
|
|
82
|
-
|
71
|
+
Now you can use `UserCloner` to clone existing records:
|
83
72
|
|
84
73
|
```ruby
|
85
|
-
|
86
|
-
|
74
|
+
user = User.last
|
75
|
+
#=> <#User(login: 'clown', email: 'clown@circus.example.com')>
|
76
|
+
|
77
|
+
cloned = UserCloner.call(user, email: 'fake@example.com')
|
78
|
+
cloned.persisted?
|
87
79
|
# => false
|
88
|
-
|
89
|
-
|
80
|
+
|
81
|
+
cloned.save!
|
82
|
+
cloned.login
|
90
83
|
# => nil
|
91
|
-
|
84
|
+
cloned.email
|
92
85
|
# => "fake@example.com"
|
93
86
|
|
94
87
|
# associations:
|
95
|
-
|
88
|
+
cloned.posts.count == user.posts.count
|
96
89
|
# => true
|
97
|
-
|
90
|
+
cloned.profile.name
|
98
91
|
# => nil
|
99
92
|
```
|
100
93
|
|
101
|
-
|
102
|
-
|
103
|
-
- [Configuration](#configuration)
|
104
|
-
- [Include one association](#include_association)
|
105
|
-
- - [Scope](#include_association_scope)
|
106
|
-
- - [Options](#include_association_options)
|
107
|
-
- [Exclude association](#exclude_association)
|
108
|
-
- [Nullify attribute(s)](#nullify)
|
109
|
-
- [Execute finalize block](#finalize)
|
110
|
-
- [Traits](#traits)
|
111
|
-
- [Execution order](#execution_order)
|
112
|
-
- [Customization](#customization)
|
94
|
+
Take a look at our [documentation](https://palkan.github.io/clowne) for more information!
|
113
95
|
|
114
|
-
###
|
96
|
+
### Supported ORM adapters
|
115
97
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
Clowne.default_adapter = :active_record
|
121
|
-
```
|
122
|
-
|
123
|
-
### <a name="include_association"></a>Include one association
|
124
|
-
|
125
|
-
Powerful declaration for including model's association.
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
class User < ActiveRecord::Base
|
129
|
-
has_one :profile
|
130
|
-
end
|
131
|
-
|
132
|
-
class UserCloner < Clowne::Cloner
|
133
|
-
adapter Clowne::ActiveRecord::Adapter
|
134
|
-
|
135
|
-
include_association :profile
|
136
|
-
end
|
137
|
-
```
|
138
|
-
|
139
|
-
But it's not all! :) The DSL looks like
|
140
|
-
|
141
|
-
```ruby
|
142
|
-
include_association name, scope, options
|
143
|
-
```
|
144
|
-
|
145
|
-
#### <a name="include_association_scope"></a>Include one association: Scope
|
146
|
-
Scope can be a:
|
147
|
-
|
148
|
-
`Symbol` - named scope.
|
149
|
-
|
150
|
-
`Proc` - custom scope (supports parameter passing).
|
151
|
-
|
152
|
-
Example:
|
153
|
-
|
154
|
-
```ruby
|
155
|
-
class User < ActiveRecord::Base
|
156
|
-
has_many :accounts
|
157
|
-
has_many :posts
|
158
|
-
end
|
159
|
-
|
160
|
-
class Account < ActiveRecord::Base
|
161
|
-
scope :active, -> where(active: true)
|
162
|
-
end
|
163
|
-
|
164
|
-
class Post < ActiveRecord::Base
|
165
|
-
# t.string :status
|
166
|
-
end
|
167
|
-
|
168
|
-
class UserCloner < Clowne::Cloner
|
169
|
-
adapter Clowne::ActiveRecord::Adapter
|
170
|
-
|
171
|
-
include_association :accounts, :active
|
172
|
-
include_association :posts, ->(params) { where(state: params[:post_status] }
|
173
|
-
end
|
174
|
-
|
175
|
-
# posts will be cloned only with draft status
|
176
|
-
UserCloner.call(user, { post_status: :draft })
|
177
|
-
# => <#User...
|
178
|
-
```
|
179
|
-
|
180
|
-
#### <a name="include_association_options"></a>Include one association: Options
|
181
|
-
|
182
|
-
Options keys can be a:
|
183
|
-
|
184
|
-
`:clone_with` - use custom cloner for all children.
|
185
|
-
|
186
|
-
`:traits` - define special traits.
|
187
|
-
|
188
|
-
Example:
|
189
|
-
|
190
|
-
```ruby
|
191
|
-
class User < ActiveRecord::Base
|
192
|
-
has_many :posts
|
193
|
-
end
|
194
|
-
|
195
|
-
class Post < ActiveRecord::Base
|
196
|
-
# t.string :title
|
197
|
-
has_many :tags
|
198
|
-
end
|
199
|
-
```
|
200
|
-
|
201
|
-
```ruby
|
202
|
-
class PostSpecialCloner < Clowne::Cloner
|
203
|
-
adapter :active_record
|
204
|
-
|
205
|
-
nullify :title
|
206
|
-
|
207
|
-
trait :with_tags do
|
208
|
-
include_association :tags
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
class UserCloner < Clowne::Cloner
|
213
|
-
adapter :active_record
|
214
|
-
|
215
|
-
include_association :posts, clone_with: PostSpecialCloner
|
216
|
-
# or clone user's posts with tags!
|
217
|
-
# include_association :posts, clone_with: PostSpecialCloner, traits: :with_tags
|
218
|
-
end
|
219
|
-
|
220
|
-
UserCloner.call(user)
|
221
|
-
# => <#User...
|
222
|
-
```
|
223
|
-
|
224
|
-
**Notice: if custom cloner is not defined, clowne tries to find default cloner and use it. (PostCloner for previous example)**
|
225
|
-
|
226
|
-
### <a name="exclude_association"></a>Exclude association
|
227
|
-
|
228
|
-
Exclude association from copying
|
229
|
-
|
230
|
-
```ruby
|
231
|
-
class UserCloner < Clowne::Cloner
|
232
|
-
adapter Clowne::ActiveRecord::Adapter
|
233
|
-
|
234
|
-
include_association :posts
|
235
|
-
|
236
|
-
trait :without_posts do
|
237
|
-
exclude_association :posts
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# copy user and posts
|
242
|
-
clone = UserCloner.call(user)
|
243
|
-
clone.posts.count == user.posts.count
|
244
|
-
# => true
|
245
|
-
|
246
|
-
# copy only user
|
247
|
-
clone2 = UserCloner.call(user, traits: :without_posts)
|
248
|
-
clone2.posts
|
249
|
-
# => []
|
250
|
-
```
|
251
|
-
|
252
|
-
**NOTE**: once excluded association cannot be re-included, e.g. the following cloner:
|
253
|
-
|
254
|
-
```ruby
|
255
|
-
class UserCloner < Clowne::Cloner
|
256
|
-
exclude_association :comments
|
257
|
-
|
258
|
-
trait :with_comments do
|
259
|
-
# That wouldn't work
|
260
|
-
include_association :comments
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
clone = UserCloner.call(user, traits: :with_comments)
|
265
|
-
clone.comments.empty? #=> true
|
266
|
-
```
|
267
|
-
|
268
|
-
Why so? That allows to have deterministic cloning plans when combining multiple traits
|
269
|
-
(or inheriting cloners).
|
270
|
-
|
271
|
-
### <a name="nullify"></a>Nullify attribute(s)
|
272
|
-
|
273
|
-
Nullify attributes:
|
274
|
-
|
275
|
-
```ruby
|
276
|
-
class User < ActiveRecord::Base
|
277
|
-
# t.string :name
|
278
|
-
# t.string :surename
|
279
|
-
# t.string :email
|
280
|
-
end
|
281
|
-
|
282
|
-
class UserCloner < Clowne::Cloner
|
283
|
-
adapter Clowne::ActiveRecord::Adapter
|
284
|
-
|
285
|
-
nullify :name, :email
|
286
|
-
|
287
|
-
trait :nullify_surename do
|
288
|
-
nullify :surename
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
# nullify only name
|
293
|
-
clone = UserCloner.call(user)
|
294
|
-
clone.name.nil?
|
295
|
-
# => true
|
296
|
-
clone.email.nil?
|
297
|
-
# => true
|
298
|
-
clone.surename.nil?
|
299
|
-
# => false
|
300
|
-
|
301
|
-
# nullify name and surename
|
302
|
-
clone2 = UserCloner.call(user, traits: :nullify_surename)
|
303
|
-
clone.name.nil?
|
304
|
-
# => true
|
305
|
-
clone.surename.nil?
|
306
|
-
# => true
|
307
|
-
```
|
308
|
-
|
309
|
-
### <a name="finalize"></a>Execute finalize block
|
310
|
-
|
311
|
-
Simple callback for changing record manually.
|
312
|
-
|
313
|
-
```ruby
|
314
|
-
class UserCloner < Clowne::Cloner
|
315
|
-
adapter Clowne::ActiveRecord::Adapter
|
316
|
-
|
317
|
-
finalize do |source, record, params|
|
318
|
-
record.name = 'This is copy!'
|
319
|
-
end
|
320
|
-
|
321
|
-
trait :change_email do
|
322
|
-
finalize do |source, record, params|
|
323
|
-
record.email = params[:email]
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
# execute first finalize
|
329
|
-
clone = UserCloner.call(user)
|
330
|
-
clone.name
|
331
|
-
# => 'This is copy!'
|
332
|
-
clone.email == 'clone@example.com'
|
333
|
-
# => false
|
334
|
-
|
335
|
-
# execute both finalizes
|
336
|
-
clone2 = UserCloner.call(user, traits: :change_email)
|
337
|
-
clone.name
|
338
|
-
# => 'This is copy!'
|
339
|
-
clone.email
|
340
|
-
# => 'clone@example.com'
|
341
|
-
```
|
342
|
-
|
343
|
-
### <a name="traits"></a>Traits
|
344
|
-
|
345
|
-
Traits allow you to group cloner declarations together and then apply them (like in factory_bot).
|
346
|
-
|
347
|
-
```ruby
|
348
|
-
class UserCloner < Clowne::Cloner
|
349
|
-
adapter Clowne::ActiveRecord::Adapter
|
350
|
-
|
351
|
-
trait :with_posts do
|
352
|
-
include_association :posts
|
353
|
-
end
|
354
|
-
|
355
|
-
trait :with_profile do
|
356
|
-
include_association :profile
|
357
|
-
end
|
358
|
-
|
359
|
-
trait :nullify_name do
|
360
|
-
nullify :name
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
# execute first finalize
|
365
|
-
UserCloner.call(user, traits: [:with_posts, :with_profile, :nullify_name])
|
366
|
-
# or
|
367
|
-
UserCloner.call(user, traits: :nullify_name)
|
368
|
-
# or
|
369
|
-
# ...
|
370
|
-
```
|
371
|
-
|
372
|
-
### <a name="execution_order"></a>Execution order
|
373
|
-
|
374
|
-
The order of cloning actions depends on the adapter.
|
375
|
-
|
376
|
-
For ActiveRecord:
|
377
|
-
- clone associations
|
378
|
-
- nullify attributes
|
379
|
-
- run `finalize` blocks
|
380
|
-
The order of `finalize` blocks is the order they've been written.
|
381
|
-
|
382
|
-
### <a name="customization"></a>Customization
|
383
|
-
|
384
|
-
Clowne is built with extensibility in mind. You can create your own DSL commands and resolvers.
|
385
|
-
|
386
|
-
Let's consider an example.
|
387
|
-
|
388
|
-
Suppose that you want to add `include_all` declaration to automagically include all associations (for ActiveRecord).
|
389
|
-
|
390
|
-
First, you should add a custom declaration:
|
391
|
-
|
392
|
-
```ruby
|
393
|
-
class IncludeAll # :nodoc: all
|
394
|
-
def compile(plan)
|
395
|
-
# Just add all_associations object to plan
|
396
|
-
plan.set(:all_associations, self)
|
397
|
-
# Plan supports 3 types of registers:
|
398
|
-
#
|
399
|
-
# 1) Scalar
|
400
|
-
#
|
401
|
-
# plan.set(key, value)
|
402
|
-
# plan.remove(key)
|
403
|
-
#
|
404
|
-
# 2) Append-only lists
|
405
|
-
#
|
406
|
-
# plan.add(key, value)
|
407
|
-
#
|
408
|
-
# 3) Two-phase set (2P-Set) (see below)
|
409
|
-
#
|
410
|
-
# plan.add_to(type, key, value)
|
411
|
-
# plan.remove_from(type, key)
|
412
|
-
end
|
413
|
-
end
|
414
|
-
|
415
|
-
# Register our declrations, i.e. extend DSL
|
416
|
-
Clowne::Declarations.add :include_all, Clowne::Declarations::IncludeAll
|
417
|
-
```
|
418
|
-
|
419
|
-
\* Operations over [2P-Set](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type#2P-Set_(Two-Phase_Set)) (adding/removing) do not depend on the order of execution; we use "remove-wins" semantics, i.e. when a key has been removed, it cannot be re-added.
|
420
|
-
|
421
|
-
Secondly, register a resolver:
|
422
|
-
|
423
|
-
```ruby
|
424
|
-
class AllAssociations
|
425
|
-
# This method is called when all_associations command is applied.
|
426
|
-
#
|
427
|
-
# source – source record
|
428
|
-
# record – target record (our clone)
|
429
|
-
# declaration – declaration object
|
430
|
-
# params – custom params passed to cloner
|
431
|
-
def call(source, record, declaration, params:)
|
432
|
-
source.class.reflections.each do |name, reflection|
|
433
|
-
# Exclude belongs_to associations
|
434
|
-
next if reflection.macro == :belongs_to
|
435
|
-
# Resolve and apply association cloner
|
436
|
-
cloner_class = Clowne::Adapters::ActiveRecord::Associations.cloner_for(reflection)
|
437
|
-
cloner_class.new(reflection, source, declaration, params).call(record)
|
438
|
-
end
|
439
|
-
record
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
# Finally, register the resolver
|
444
|
-
Clowne::Adapters::ActiveRecord.register_resolver(
|
445
|
-
:all_associations, AllAssociations
|
446
|
-
)
|
447
|
-
```
|
448
|
-
|
449
|
-
Now you can use it likes this:
|
450
|
-
|
451
|
-
```ruby
|
452
|
-
class UserCloner < Clowne::Cloner
|
453
|
-
adapter :active_record
|
454
|
-
|
455
|
-
include_all
|
456
|
-
end
|
457
|
-
```
|
98
|
+
Adapter |1:1 | 1:M | M:M |
|
99
|
+
------------------------------------------|------------|-------------|-------------------------|
|
100
|
+
[Active Record](https://palkan.github.io/clowne/docs/active_record.html) | has_one | has_many | has_and_belongs_to_many |
|
101
|
+
[Sequel](https://palkan.github.io/clowne/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
|
458
102
|
|
459
103
|
## Maintainers
|
460
104
|
|