clowne 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
```
|