clowne 1.3.0 → 1.5.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/CHANGELOG.md +22 -1
- data/README.md +1 -4
- data/lib/clowne/adapters/active_record/associations/belongs_to.rb +0 -1
- data/lib/clowne/adapters/active_record/associations/has_one.rb +0 -1
- data/lib/clowne/adapters/active_record/associations/noop.rb +1 -1
- data/lib/clowne/adapters/active_record/associations.rb +1 -1
- data/lib/clowne/adapters/active_record/resolvers/association.rb +0 -1
- data/lib/clowne/adapters/base/association.rb +1 -1
- data/lib/clowne/adapters/base.rb +2 -2
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +0 -4
- data/lib/clowne/adapters/sequel/associations.rb +1 -1
- data/lib/clowne/adapters/sequel/resolvers/association.rb +0 -1
- data/lib/clowne/cloner.rb +1 -1
- data/lib/clowne/declarations/trait.rb +1 -1
- data/lib/clowne/declarations.rb +1 -1
- data/lib/clowne/ext/record_key.rb +1 -1
- data/lib/clowne/resolvers/after_clone.rb +2 -1
- data/lib/clowne/resolvers/init_as.rb +0 -1
- data/lib/clowne/rspec/clone_association.rb +0 -1
- data/lib/clowne/version.rb +1 -1
- data/lib/clowne.rb +2 -3
- metadata +7 -110
- data/.codeclimate.yml +0 -7
- data/.gitattributes +0 -1
- data/.gitignore +0 -16
- data/.rspec +0 -3
- data/.rubocop.yml +0 -28
- data/.rufo +0 -3
- data/.travis.yml +0 -45
- data/Gemfile +0 -20
- data/Rakefile +0 -8
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/clowne.gemspec +0 -36
- data/docs/.nojekyll +0 -0
- data/docs/.rubocop.yml +0 -18
- data/docs/CNAME +0 -1
- data/docs/README.md +0 -131
- data/docs/_sidebar.md +0 -25
- data/docs/active_record.md +0 -33
- data/docs/after_clone.md +0 -53
- data/docs/after_persist.md +0 -77
- data/docs/architecture.md +0 -138
- data/docs/assets/docsify.min.js +0 -1
- data/docs/assets/prism-ruby.min.js +0 -1
- data/docs/assets/styles.css +0 -348
- data/docs/assets/vue.css +0 -1
- data/docs/clone_mapper.md +0 -59
- data/docs/customization.md +0 -63
- data/docs/exclude_association.md +0 -61
- data/docs/finalize.md +0 -31
- data/docs/from_v02_to_v1.md +0 -83
- data/docs/getting_started.md +0 -171
- data/docs/implicit_cloner.md +0 -33
- data/docs/include_association.md +0 -133
- data/docs/index.html +0 -29
- data/docs/init_as.md +0 -40
- data/docs/inline_configuration.md +0 -37
- data/docs/nullify.md +0 -33
- data/docs/operation.md +0 -55
- data/docs/parameters.md +0 -112
- data/docs/sequel.md +0 -50
- data/docs/supported_adapters.md +0 -10
- data/docs/testing.md +0 -194
- data/docs/traits.md +0 -25
- data/gemfiles/activerecord42.gemfile +0 -9
- data/gemfiles/jruby.gemfile +0 -10
- data/gemfiles/railsmaster.gemfile +0 -10
- data/lib/clowne/ext/yield_self_then.rb +0 -25
data/docs/customization.md
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
# Customization
|
2
|
-
|
3
|
-
Clowne is built with extensibility in mind. You can create your own DSL commands and resolvers.
|
4
|
-
|
5
|
-
Let's consider an example.
|
6
|
-
|
7
|
-
Suppose that you want to add the `include_all` declaration to automagically include all associations (for ActiveRecord).
|
8
|
-
|
9
|
-
First, you should add a custom declaration:
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
# Extend from Base declaration
|
13
|
-
class IncludeAll < Clowne::Declarations::Base # :nodoc: all
|
14
|
-
def compile(plan)
|
15
|
-
# Just add all_associations declaration (self) to plan
|
16
|
-
plan.set(:all_associations, self)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Register our declrations, i.e. extend DSL
|
21
|
-
Clowne::Declarations.add :include_all, IncludeAll
|
22
|
-
```
|
23
|
-
|
24
|
-
See more on `plan` in [architecture overview](architecture.md).
|
25
|
-
|
26
|
-
Secondly, register a resolver:
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
class AllAssociations
|
30
|
-
# This method is called when all_associations command is applied.
|
31
|
-
#
|
32
|
-
# source – source record
|
33
|
-
# record – target record (our clone)
|
34
|
-
# declaration – declaration object
|
35
|
-
# params – custom params passed to cloner
|
36
|
-
def call(source, record, declaration, params:)
|
37
|
-
source.class.reflections.each_value do |_name, reflection|
|
38
|
-
# Exclude belongs_to associations
|
39
|
-
next if reflection.macro == :belongs_to
|
40
|
-
|
41
|
-
# Resolve and apply association cloner
|
42
|
-
cloner_class = Clowne::Adapters::ActiveRecord::Associations.cloner_for(reflection)
|
43
|
-
cloner_class.new(reflection, source, declaration, params).call(record)
|
44
|
-
end
|
45
|
-
record
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Finally, register the resolver
|
50
|
-
Clowne::Adapters::ActiveRecord.register_resolver(
|
51
|
-
:all_associations, AllAssociations
|
52
|
-
)
|
53
|
-
```
|
54
|
-
|
55
|
-
Now you can use it likes this:
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
class UserCloner < Clowne::Cloner
|
59
|
-
adapter :active_record
|
60
|
-
|
61
|
-
include_all
|
62
|
-
end
|
63
|
-
```
|
data/docs/exclude_association.md
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# Exclude Association
|
2
|
-
|
3
|
-
Clowne doesn't include any association by default and doesn't provide _magic_ `include_all` declaration (although you can [add one by yourself](customization.md)).
|
4
|
-
|
5
|
-
Nevertheless, sometimes you might want to exclude already added associations (when inheriting a cloner or using [traits](traits.md)).
|
6
|
-
|
7
|
-
Consider an example:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
class UserCloner < Clowne::Cloner
|
11
|
-
include_association :posts
|
12
|
-
|
13
|
-
trait :without_posts do
|
14
|
-
exclude_association :posts
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# copy user and posts
|
19
|
-
clone = UserCloner.call(user).to_record
|
20
|
-
clone.posts.count == user.posts.count
|
21
|
-
# => true
|
22
|
-
|
23
|
-
# copy only user
|
24
|
-
clone2 = UserCloner.call(user, traits: :without_posts).to_record
|
25
|
-
clone2.posts
|
26
|
-
# => []
|
27
|
-
```
|
28
|
-
|
29
|
-
**NOTE**: once excluded association cannot be re-included, e.g. the following cloner:
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
class UserCloner < Clowne::Cloner
|
33
|
-
exclude_association :comments
|
34
|
-
|
35
|
-
trait :with_comments do
|
36
|
-
# That wouldn't work
|
37
|
-
include_association :comments
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
clone = UserCloner.call(user, traits: :with_comments).to_record
|
42
|
-
clone.comments.empty?
|
43
|
-
# => true
|
44
|
-
```
|
45
|
-
|
46
|
-
Why so? That allows us to have a deterministic cloning plan when combining multiple traits
|
47
|
-
(or inheriting cloners).
|
48
|
-
|
49
|
-
## Exclude multiple associations
|
50
|
-
|
51
|
-
It's possible to exclude multiple associations at once the same way as [`include_associations`](include_association.md):
|
52
|
-
|
53
|
-
```ruby
|
54
|
-
class UserCloner < Clowne::Cloner
|
55
|
-
include_associations :accounts, :posts, :comments
|
56
|
-
|
57
|
-
trait :without_posts do
|
58
|
-
exclude_associations :posts, :comments
|
59
|
-
end
|
60
|
-
end
|
61
|
-
```
|
data/docs/finalize.md
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
# Finalization
|
2
|
-
|
3
|
-
To apply custom transformations to the cloned record, you can use the `finalize` declaration:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
class UserCloner < Clowne::Cloner
|
7
|
-
finalize do |_source, record, **_params|
|
8
|
-
record.name = "This is copy!"
|
9
|
-
end
|
10
|
-
|
11
|
-
trait :change_email do
|
12
|
-
finalize do |_source, record, **params|
|
13
|
-
record.email = params[:email]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
cloned = UserCloner.call(user).to_record
|
19
|
-
cloned.name
|
20
|
-
# => 'This is copy!'
|
21
|
-
cloned.email == "clone@example.com"
|
22
|
-
# => false
|
23
|
-
|
24
|
-
cloned2 = UserCloner.call(user, traits: :change_email).to_record
|
25
|
-
cloned2.name
|
26
|
-
# => 'This is copy!'
|
27
|
-
cloned2.email
|
28
|
-
# => 'clone@example.com'
|
29
|
-
```
|
30
|
-
|
31
|
-
Finalization blocks are called at the end of the [cloning process](getting_started?id=execution-order).
|
data/docs/from_v02_to_v1.md
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
# From v0.2.x to v1.0.0
|
2
|
-
|
3
|
-
The breaking change of v1.0 is the return of a unified [`result object`](operation.md) for all adapters.
|
4
|
-
|
5
|
-
## ActiveRecord
|
6
|
-
|
7
|
-
### Update code to work with [`Operation`](operation.md)
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
# Before
|
11
|
-
clone = UserCloner.call(user)
|
12
|
-
# => <#User id: nil, ...>
|
13
|
-
clone.save!
|
14
|
-
# => true
|
15
|
-
|
16
|
-
# After
|
17
|
-
clone = UserCloner.call(user)
|
18
|
-
# => <#Clowne::Utils::Operation ...>
|
19
|
-
clone = clone.to_record
|
20
|
-
# => <#User id: 2, ...>
|
21
|
-
clone.save!
|
22
|
-
# => true
|
23
|
-
|
24
|
-
# After (even better because of using full functionality)
|
25
|
-
operation = UserCloner.call(user)
|
26
|
-
# => <#Clowne::Utils::Operation ...>
|
27
|
-
operation.persist!
|
28
|
-
# => true
|
29
|
-
clone = operation.to_record
|
30
|
-
# => <#User id: 2, ...>
|
31
|
-
clone.persisted?
|
32
|
-
# => true
|
33
|
-
```
|
34
|
-
|
35
|
-
### Move post-processing cloning logic into [`after_persist`](after_persist.md) callback (if you have it)
|
36
|
-
|
37
|
-
*Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter.*
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
# Before
|
41
|
-
clone = UserCloner.call(user)
|
42
|
-
clone.save!
|
43
|
-
# do something with persisted clone
|
44
|
-
|
45
|
-
# After
|
46
|
-
class UserCloner < Clowne::Cloner
|
47
|
-
# ...
|
48
|
-
after_persist do |origin, clone, **|
|
49
|
-
# do something with persisted clone
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
clone = UserCloner.call(user).tap(&:persist).to_record
|
54
|
-
```
|
55
|
-
## Sequel
|
56
|
-
|
57
|
-
### Use `to_record` instead of `to_model`
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
# Before
|
61
|
-
record_wrapper = UserCloner.call(user)
|
62
|
-
clone = record_wrapper.to_model
|
63
|
-
clone.new?
|
64
|
-
# => true
|
65
|
-
|
66
|
-
# After
|
67
|
-
operation = UserCloner.call(user)
|
68
|
-
clone = operation.to_record
|
69
|
-
clone.new?
|
70
|
-
# => true
|
71
|
-
```
|
72
|
-
|
73
|
-
### Use `operation#persist` instead of converting to model and calling `#save`
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
# Before
|
77
|
-
record_wrapper = UserCloner.call(user)
|
78
|
-
clone = record_wrapper.to_model
|
79
|
-
clone.save
|
80
|
-
|
81
|
-
# After
|
82
|
-
clone = UserCloner.call(user).tap(&:persist).to_record
|
83
|
-
```
|
data/docs/getting_started.md
DELETED
@@ -1,171 +0,0 @@
|
|
1
|
-
# Getting Started
|
2
|
-
|
3
|
-
## Installation
|
4
|
-
|
5
|
-
To install Clowne with RubyGems:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
gem install clowne
|
9
|
-
```
|
10
|
-
|
11
|
-
Or add this line to your application's Gemfile:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
gem "clowne"
|
15
|
-
```
|
16
|
-
|
17
|
-
## Configuration
|
18
|
-
|
19
|
-
Basic cloner implementation looks like:
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
class SomeCloner < Clowne::Cloner
|
23
|
-
adapter :active_record # or adapter Clowne::Adapters::ActiveRecord
|
24
|
-
# some implementation ...
|
25
|
-
end
|
26
|
-
```
|
27
|
-
|
28
|
-
You can configure the default adapter for cloners:
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
# put to initializer
|
32
|
-
# e.g. config/initializers/clowne.rb
|
33
|
-
Clowne.default_adapter = :active_record
|
34
|
-
```
|
35
|
-
|
36
|
-
and skip explicit adapter declaration
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
class SomeCloner < Clowne::Cloner
|
40
|
-
# some implementation ...
|
41
|
-
end
|
42
|
-
```
|
43
|
-
See the list of [available adapters](supported_adapters.md).
|
44
|
-
|
45
|
-
## Basic Example
|
46
|
-
|
47
|
-
Assume that you have the following model:
|
48
|
-
|
49
|
-
```ruby
|
50
|
-
class User < ActiveRecord::Base
|
51
|
-
# create_table :users do |t|
|
52
|
-
# t.string :login
|
53
|
-
# t.string :email
|
54
|
-
# t.timestamps null: false
|
55
|
-
# end
|
56
|
-
|
57
|
-
has_one :profile
|
58
|
-
has_many :posts
|
59
|
-
end
|
60
|
-
|
61
|
-
class Profile < ActiveRecord::Base
|
62
|
-
# create_table :profiles do |t|
|
63
|
-
# t.string :name
|
64
|
-
# end
|
65
|
-
end
|
66
|
-
|
67
|
-
class Post < ActiveRecord::Base
|
68
|
-
# create_table :posts
|
69
|
-
end
|
70
|
-
```
|
71
|
-
|
72
|
-
Let's declare our cloners first:
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
class UserCloner < Clowne::Cloner
|
76
|
-
adapter :active_record
|
77
|
-
|
78
|
-
include_association :profile, clone_with: SpecialProfileCloner
|
79
|
-
include_association :posts
|
80
|
-
|
81
|
-
nullify :login
|
82
|
-
|
83
|
-
# params here is an arbitrary Hash passed into cloner
|
84
|
-
finalize do |_source, record, **params|
|
85
|
-
record.email = params[:email]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
class SpecialProfileCloner < Clowne::Cloner
|
90
|
-
adapter :active_record
|
91
|
-
|
92
|
-
nullify :name
|
93
|
-
end
|
94
|
-
```
|
95
|
-
|
96
|
-
Now you can use `UserCloner` to clone existing records:
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
user = User.last
|
100
|
-
# => <#User id: 1, login: 'clown', email: 'clown@circus.example.com'>
|
101
|
-
|
102
|
-
operation = UserCloner.call(user, email: "fake@example.com")
|
103
|
-
# => <#Clowne::Utils::Operation...>
|
104
|
-
|
105
|
-
operation.to_record
|
106
|
-
# => <#User id: nil, login: nil, email: 'fake@example.com'>
|
107
|
-
|
108
|
-
operation.persist!
|
109
|
-
# => true
|
110
|
-
|
111
|
-
cloned = operation.to_record
|
112
|
-
# => <#User id: 2, login: nil, email: 'fake@example.com'>
|
113
|
-
|
114
|
-
cloned.login
|
115
|
-
# => nil
|
116
|
-
cloned.email
|
117
|
-
# => "fake@example.com"
|
118
|
-
|
119
|
-
# associations:
|
120
|
-
cloned.posts.count == user.posts.count
|
121
|
-
# => true
|
122
|
-
cloned.profile.name
|
123
|
-
# => nil
|
124
|
-
```
|
125
|
-
|
126
|
-
## Overview
|
127
|
-
|
128
|
-
In [the basic example](#basic-example), you can see that Clowne consists of flexible DSL which is used in a class inherited of `Clowne::Cloner`.
|
129
|
-
|
130
|
-
You can combinate this DSL via [`traits`](traits.md) and make a cloning plan which exactly you want.
|
131
|
-
|
132
|
-
**We strongly recommend [`write tests`](testing.md) to cover resulting cloner logic**
|
133
|
-
|
134
|
-
Cloner class returns [`Operation`](operation.md) instance as a result of cloning. The operation provides methods to save cloned record. You can wrap this call to a transaction if it is necessary.
|
135
|
-
|
136
|
-
### Execution Order
|
137
|
-
|
138
|
-
The order of cloning actions depends on the adapter (i.e., could be customized).
|
139
|
-
|
140
|
-
All built-in adapters have the same order and what happens when you call `Operation#persist`:
|
141
|
-
- init clone (see [`init_as`](init_as.md)) (empty by default)
|
142
|
-
- [`clone associations`](include_association.md)
|
143
|
-
- [`nullify`](nullify.md) attributes
|
144
|
-
- run [`finalize`](finalize.md) blocks. _The order of [`finalize`](finalize.md) blocks is the order they've been written._
|
145
|
-
- run [`after_clone`](after_clone.md) callbacks
|
146
|
-
- __SAVE CLONED RECORD__
|
147
|
-
- run [`after_persist`](after_persist.md) callbacks
|
148
|
-
|
149
|
-
## Motivation & Alternatives
|
150
|
-
|
151
|
-
### Why did we decide to build our own cloning gem instead of using the existing solutions?
|
152
|
-
|
153
|
-
First, the existing solutions turned out not to be stable and flexible enough for us.
|
154
|
-
|
155
|
-
Secondly, they are Rails-only (or, more precisely, ActiveRecord-only).
|
156
|
-
|
157
|
-
Nevertheless, thanks to [amoeba](https://github.com/amoeba-rb/amoeba) and [deep_cloneable](https://github.com/moiristo/deep_cloneable) for inspiration.
|
158
|
-
|
159
|
-
For ActiveRecord we support amoeba-like [in-model configuration](active_record.md) and you can add missing DSL declarations yourself [easily](customization.md).
|
160
|
-
|
161
|
-
We also provide an ability to specify cloning [configuration in-place](inline_configuration.md) like `deep_clonable` does.
|
162
|
-
|
163
|
-
So, we took the best of these too and brought to the outside-of-Rails world.
|
164
|
-
|
165
|
-
### Why build a gem to clone models at all?
|
166
|
-
|
167
|
-
That's a good question. Of course, you can write plain old Ruby services do handle the cloning logic. But for complex models hierarchies, this approach has major disadvantages: high code complexity and lack of re-usability.
|
168
|
-
|
169
|
-
The things become even worse when you deal with STI models and different cloning contexts.
|
170
|
-
|
171
|
-
That's why we decided to build a specific cloning tool.
|
data/docs/implicit_cloner.md
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# Implicit Cloner
|
2
|
-
|
3
|
-
When [cloning associations](include_association.md) Clowne tries to infer an appropriate cloner class for the records (unless `clone_with` specified).
|
4
|
-
|
5
|
-
It relies on the naming convention: `MyModel` -> `MyModelCloner`.
|
6
|
-
|
7
|
-
Consider an example:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
class User < ActiveRecord::Base
|
11
|
-
has_one :profile
|
12
|
-
end
|
13
|
-
|
14
|
-
class UserCloner < Clowne::Cloner
|
15
|
-
include_association :profile
|
16
|
-
end
|
17
|
-
|
18
|
-
class ProfileCloner < Clowne::Cloner
|
19
|
-
finalize do |source, record|
|
20
|
-
record.name = "Clone of #{source.name}"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
user = User.last
|
25
|
-
user.profile.name
|
26
|
-
#=> "Bimbo"
|
27
|
-
|
28
|
-
cloned = UserCloner.call(user).to_record
|
29
|
-
cloned.profile.name
|
30
|
-
# => "Clone of Bimbo"
|
31
|
-
```
|
32
|
-
|
33
|
-
**NOTE:** when using [in-model cloner](active_record.md) for ActiveRecord it is used by default.
|
data/docs/include_association.md
DELETED
@@ -1,133 +0,0 @@
|
|
1
|
-
# Include Association
|
2
|
-
|
3
|
-
Use this declaration to clone model's associations:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
class User < ActiveRecord::Base
|
7
|
-
has_one :profile
|
8
|
-
end
|
9
|
-
|
10
|
-
class UserCloner < Clowne::Cloner
|
11
|
-
include_association :profile
|
12
|
-
end
|
13
|
-
```
|
14
|
-
|
15
|
-
Looks pretty simple, right? But that's not all we may offer you! :)
|
16
|
-
|
17
|
-
The declaration supports additional arguments:
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
include_association name, scope, options
|
21
|
-
```
|
22
|
-
|
23
|
-
### Supported Associations
|
24
|
-
|
25
|
-
Adapter |1:1 |*:1 | 1:M | M:M |
|
26
|
-
------------------------------------------|------------|------------|-------------|-------------------------|
|
27
|
-
[Active Record](active_record) | has_one | belongs_to | has_many | has_and_belongs_to|
|
28
|
-
[Sequel](sequel) | one_to_one | - | one_to_many | many_to_many |
|
29
|
-
|
30
|
-
## Scope
|
31
|
-
|
32
|
-
Scope can be a:
|
33
|
-
- `Symbol` - named scope.
|
34
|
-
- `Proc` - custom scope (supports parameters).
|
35
|
-
|
36
|
-
Example:
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
class User < ActiveRecord::Base
|
40
|
-
has_many :accounts
|
41
|
-
has_many :posts
|
42
|
-
end
|
43
|
-
|
44
|
-
class Account < ActiveRecord::Base
|
45
|
-
scope :active, -> { where(active: true) }
|
46
|
-
end
|
47
|
-
|
48
|
-
class Post < ActiveRecord::Base
|
49
|
-
# t.string :status
|
50
|
-
end
|
51
|
-
|
52
|
-
class UserCloner < Clowne::Cloner
|
53
|
-
include_association :accounts, :active
|
54
|
-
include_association :posts, ->(params) { where(state: params[:state]) }
|
55
|
-
end
|
56
|
-
|
57
|
-
# Clone only draft posts
|
58
|
-
UserCloner.call(user, state: :draft).to_record
|
59
|
-
# => <#User id: nil, ... >
|
60
|
-
```
|
61
|
-
|
62
|
-
## Options
|
63
|
-
|
64
|
-
The following options are available:
|
65
|
-
- `:clone_with` - use custom cloner\*
|
66
|
-
- `:traits` - define special traits.
|
67
|
-
|
68
|
-
\* **NOTE:** the same cloner class would be used for **all children**
|
69
|
-
|
70
|
-
Example:
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
class User < ActiveRecord::Base
|
74
|
-
has_many :posts
|
75
|
-
end
|
76
|
-
|
77
|
-
class Post < ActiveRecord::Base
|
78
|
-
# t.string :title
|
79
|
-
has_many :tags
|
80
|
-
end
|
81
|
-
```
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
class PostSpecialCloner < Clowne::Cloner
|
85
|
-
nullify :title
|
86
|
-
|
87
|
-
trait :with_tags do
|
88
|
-
include_association :tags
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
class UserCloner < Clowne::Cloner
|
93
|
-
adapter :active_record
|
94
|
-
|
95
|
-
include_association :posts, clone_with: PostSpecialCloner
|
96
|
-
# or clone user's posts with tags!
|
97
|
-
# include_association :posts, clone_with: PostSpecialCloner, traits: :with_tags
|
98
|
-
end
|
99
|
-
|
100
|
-
UserCloner.call(user).to_record
|
101
|
-
# => <#User id: nil, ... >
|
102
|
-
```
|
103
|
-
|
104
|
-
**NOTE**: if custom cloner is not defined, Clowne tries to infer the [implicit cloner](implicit_cloner.md).
|
105
|
-
|
106
|
-
## Nested parameters
|
107
|
-
|
108
|
-
Follow to [documentation page](parameters.md).
|
109
|
-
|
110
|
-
## Include multiple associations
|
111
|
-
|
112
|
-
You can include multiple associations at once too:
|
113
|
-
|
114
|
-
```ruby
|
115
|
-
class User < ActiveRecord::Base
|
116
|
-
has_many :accounts
|
117
|
-
has_many :posts
|
118
|
-
end
|
119
|
-
|
120
|
-
class UserCloner < Clowne::Cloner
|
121
|
-
adapter :active_record
|
122
|
-
|
123
|
-
include_associations :accounts, :posts
|
124
|
-
end
|
125
|
-
```
|
126
|
-
|
127
|
-
**NOTE:** in that case, it's not possible to provide custom scopes and options.
|
128
|
-
|
129
|
-
### Belongs To association
|
130
|
-
|
131
|
-
You can include belongs_to association, but will do it carefully.
|
132
|
-
If you have loop by relations in your models, when you clone it will raise SystemStackError.
|
133
|
-
Check this [test](https://github.com/palkan/clowne/blob/master/spec/clowne/integrations/active_record_belongs_to_spec.rb) for instance.
|
data/docs/index.html
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html lang="en">
|
3
|
-
<head>
|
4
|
-
<meta charset="UTF-8">
|
5
|
-
<title>Document</title>
|
6
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
-
<meta name="description" content="Description">
|
8
|
-
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
9
|
-
<link rel="stylesheet" href="assets/vue.css">
|
10
|
-
<link rel="stylesheet" href="assets/styles.css">
|
11
|
-
</head>
|
12
|
-
<body>
|
13
|
-
<div id="app"></div>
|
14
|
-
<script>
|
15
|
-
window.$docsify = {
|
16
|
-
name: 'Clowne',
|
17
|
-
repo: 'https://github.com/clowne-rb/clowne',
|
18
|
-
loadSidebar: true,
|
19
|
-
subMaxLevel: 2,
|
20
|
-
auto2top: true,
|
21
|
-
search: {
|
22
|
-
namespace: 'clowne'
|
23
|
-
}
|
24
|
-
}
|
25
|
-
</script>
|
26
|
-
<script src="assets/docsify.min.js"></script>
|
27
|
-
<script src="assets/prism-ruby.min.js"></script>
|
28
|
-
</body>
|
29
|
-
</html>
|
data/docs/init_as.md
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# Initialize Cloning Target
|
2
|
-
|
3
|
-
You can override the default Clowne method which generates a _plain_ copy of a source object.
|
4
|
-
By default, Clowne initiates the cloned record using a `#dup` method.
|
5
|
-
|
6
|
-
For example, Cloners could be used not only to generate _fresh_ new models but to apply some transformations to the existing record:
|
7
|
-
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
class User < ApplicationRecord
|
11
|
-
has_one :profile
|
12
|
-
has_many :posts
|
13
|
-
end
|
14
|
-
|
15
|
-
class UserCloner < Clowne::Cloner
|
16
|
-
adapter :active_record
|
17
|
-
|
18
|
-
include_association :profile
|
19
|
-
|
20
|
-
trait :copy_settings do
|
21
|
-
# Use a `target` for all the actions
|
22
|
-
init_as { |_source, target:| target }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
jack = User.find_by(email: "jack@evl.ms")
|
27
|
-
# => <#User id: 1, ...>
|
28
|
-
jack.create_profile(name: "Jack")
|
29
|
-
# => <#Profile id: 1, name: 'Jack', ...>
|
30
|
-
|
31
|
-
john = User.find_by(email: "john@evl.ms")
|
32
|
-
# => <#User id: 2, ...>
|
33
|
-
|
34
|
-
# we want to clone Jack's profile to John's user,
|
35
|
-
# without creating a new one
|
36
|
-
john_with_profile = UserCloner.call(jack, traits: :copy_settings, target: john).to_record
|
37
|
-
# => <#User id: 2, ...>
|
38
|
-
john_with_profile.profile
|
39
|
-
#=> <#Profile id: nil, name: 'Jack',...>
|
40
|
-
```
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# Inline Configuration
|
2
|
-
|
3
|
-
You can also enhance the cloner configuration inline (i.e., add declarations dynamically):
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
operation = UserCloner.call(User.last) do
|
7
|
-
exclude_association :profile
|
8
|
-
|
9
|
-
finalize do |source, record|
|
10
|
-
record.email = "clone_of_#{source.email}"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
cloned = operation.to_record
|
15
|
-
|
16
|
-
cloned.email
|
17
|
-
# => "clone_of_john@example.com"
|
18
|
-
|
19
|
-
# associations:
|
20
|
-
cloned.posts.size == User.last.posts.size
|
21
|
-
# => true
|
22
|
-
cloned.profile
|
23
|
-
# => nil
|
24
|
-
```
|
25
|
-
|
26
|
-
Inline enhancement doesn't affect the _global_ configuration so that you can use it without any fear.
|
27
|
-
|
28
|
-
Thus it's also possible to clone objects without any cloner classes at all by using `Clowne::Cloner`:
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
cloned = Clowne::Cloner.call(user) do
|
32
|
-
# anything you want!
|
33
|
-
end.to_record
|
34
|
-
|
35
|
-
cloned
|
36
|
-
# => <#User..
|
37
|
-
```
|