clowne 1.4.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 +6 -0
- data/README.md +0 -2
- data/lib/clowne/adapters/active_record/associations/noop.rb +1 -1
- data/lib/clowne/adapters/base/association.rb +1 -1
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +0 -4
- data/lib/clowne/ext/record_key.rb +1 -1
- data/lib/clowne/version.rb +1 -1
- metadata +7 -113
- data/.codeclimate.yml +0 -7
- data/.gitattributes +0 -1
- data/.github/workflows/rspec-jruby.yml +0 -33
- data/.github/workflows/rspec-truffle.yml +0 -35
- data/.github/workflows/rspec.yml +0 -51
- data/.github/workflows/rubocop.yml +0 -20
- data/.gitignore +0 -16
- data/.rspec +0 -3
- data/.rubocop.yml +0 -29
- data/.rufo +0 -3
- 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 -15
- 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/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
|
-
```
|
data/docs/nullify.md
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# Nullify Attributes
|
2
|
-
|
3
|
-
To set a bunch of attributes to `nil` you can use the `nullify` declaration:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
class User < ActiveRecord::Base
|
7
|
-
# t.string :name
|
8
|
-
# t.string :surname
|
9
|
-
# t.string :email
|
10
|
-
end
|
11
|
-
|
12
|
-
class UserCloner < Clowne::Cloner
|
13
|
-
nullify :name, :email
|
14
|
-
|
15
|
-
trait :nullify_surname do
|
16
|
-
nullify :surname
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
clone = UserCloner.call(user).to_record
|
21
|
-
clone.name.nil?
|
22
|
-
# => true
|
23
|
-
clone.email.nil?
|
24
|
-
# => true
|
25
|
-
clone.surname.nil?
|
26
|
-
# => false
|
27
|
-
|
28
|
-
clone2 = UserCloner.call(user, traits: :nullify_surname).to_record
|
29
|
-
clone2.name.nil?
|
30
|
-
# => true
|
31
|
-
clone2.surname.nil?
|
32
|
-
# => true
|
33
|
-
```
|
data/docs/operation.md
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# Operation
|
2
|
-
|
3
|
-
Since version 1.0 Clowne has been returning specific result object instead of a raw cloned object. It has allowed unifying interface between adapters and has opened an opportunity to implement new features. We call this object `Operation`.
|
4
|
-
|
5
|
-
An instance of `Operation` has a very clear interface:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
class User < ActiveRecord::Base; end
|
9
|
-
|
10
|
-
class UserCloner < Clowne::Cloner
|
11
|
-
nullify :email
|
12
|
-
|
13
|
-
after_persist do |_origin, cloned, **|
|
14
|
-
cloned.update(email: "evl-#{cloned.id}.ms")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
user = User.create(email: "evl.ms")
|
19
|
-
# => <#User id: 1, email: 'evl.ms', ...>
|
20
|
-
|
21
|
-
operation = UserCloner.call(user)
|
22
|
-
|
23
|
-
# Return resulted (non saved) object:
|
24
|
-
operation.to_record
|
25
|
-
# => <#User id: nil, email: nil, ...>
|
26
|
-
|
27
|
-
# Save cloned object and call after_persist callbacks:
|
28
|
-
operation.persist # or operation.persist!
|
29
|
-
# => true
|
30
|
-
|
31
|
-
operation.to_record
|
32
|
-
# => <#User id: 2, email: 'evl-2.ms', ...>
|
33
|
-
|
34
|
-
# Call only after_persist callbacks:
|
35
|
-
user2 = operation.to_record
|
36
|
-
# => <#User id: 2, email: 'evl-2.ms', ...>
|
37
|
-
user2.update(email: "admin@example.com")
|
38
|
-
# => <#User id: 2, email: 'admin@example.com' ...>
|
39
|
-
operation.run_after_persist
|
40
|
-
# => <#User id: 2, email: 'evl-2.ms', ...>
|
41
|
-
```
|
42
|
-
|
43
|
-
The last example is weird, but it can be helpful when you need to execute `save` (or `save!`) separately from `after_persist` callbacks:
|
44
|
-
|
45
|
-
```ruby
|
46
|
-
operation = UserClone.call(user)
|
47
|
-
|
48
|
-
# Wrap main cloning into the transaction
|
49
|
-
ActiveRecord::Base.transaction do
|
50
|
-
operation.to_record.save!
|
51
|
-
end
|
52
|
-
|
53
|
-
# And after that execute after_persist without transaction
|
54
|
-
operation.run_after_persist
|
55
|
-
```
|
data/docs/parameters.md
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
# Parameters
|
2
|
-
|
3
|
-
Clowne provides parameters for make your cloning logic more flexible. You can see their using in [`include_association`](include_association.md#scope) and [`finalize`](finalize.md) documentation pages.
|
4
|
-
|
5
|
-
Example:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
class UserCloner < Clowne::Cloner
|
9
|
-
include_association :posts, ->(params) { where(state: params[:state]) }
|
10
|
-
|
11
|
-
finalize do |_source, record, **params|
|
12
|
-
record.email = params[:email]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
operation = UserCloner.call(user, state: :draft, email: "cloned@example.com")
|
17
|
-
cloned = operation.to_record
|
18
|
-
cloned.email
|
19
|
-
# => 'cloned@example.com'
|
20
|
-
```
|
21
|
-
|
22
|
-
## Potential Problems
|
23
|
-
|
24
|
-
Clowne is born as a part of our big project and we use it for cloning really deep object relations. When we started to use params and forwarding them between parent-child cloners we got a nasty bugs.
|
25
|
-
|
26
|
-
As result we strongly recommend to use ruby keyword arguments instead of params hash:
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
# Bad
|
30
|
-
finalize do |_source, record, **params|
|
31
|
-
record.email = params[:email]
|
32
|
-
end
|
33
|
-
|
34
|
-
# Good
|
35
|
-
finalize do |_source, record, email:, **|
|
36
|
-
record.email = email
|
37
|
-
end
|
38
|
-
```
|
39
|
-
|
40
|
-
## Nested Parameters
|
41
|
-
|
42
|
-
Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/clowne-rb/clowne/issues/15)).
|
43
|
-
|
44
|
-
Let's explain what the difference:
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
class UserCloner < Clowne::Cloner
|
48
|
-
# Don't pass parameters to associations
|
49
|
-
trait :default do
|
50
|
-
include_association :profile
|
51
|
-
# equal to include_association :profile, params: false
|
52
|
-
end
|
53
|
-
|
54
|
-
# Pass all parameters to associations
|
55
|
-
trait :all_params do
|
56
|
-
include_association :profile, params: true
|
57
|
-
end
|
58
|
-
|
59
|
-
# Filter parameters by key.
|
60
|
-
# Notice: value by key must be a Hash.
|
61
|
-
|
62
|
-
trait :by_key do
|
63
|
-
include_association :profile, params: :profile
|
64
|
-
end
|
65
|
-
|
66
|
-
# Execute custom block with params as argument
|
67
|
-
trait :by_block do
|
68
|
-
include_association :profile, params: Proc.new do |params|
|
69
|
-
params[:profile].map { |k, v| [k, v.upcase] }.to_h
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Execute custom block with params and parent record as arguments
|
74
|
-
trait :by_block_with_parent do
|
75
|
-
include_association :profile, params: Proc.new do |params, user|
|
76
|
-
{
|
77
|
-
name: params[:profile][:name],
|
78
|
-
email: user.email
|
79
|
-
}
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class ProfileCloner < Clowne::Cloner
|
85
|
-
finalize do |_source, record, **params|
|
86
|
-
record.jsonb_field = params
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# Execute:
|
91
|
-
|
92
|
-
def get_profile_jsonb(user, trait)
|
93
|
-
params = {profile: {name: "John", surname: "Cena"}}
|
94
|
-
cloned = UserCloner.call(user, traits: trait, **params).to_record
|
95
|
-
cloned.profile.jsonb_field
|
96
|
-
end
|
97
|
-
|
98
|
-
get_profile_jsonb(user, :default)
|
99
|
-
# => {}
|
100
|
-
|
101
|
-
get_profile_jsonb(user, :all_params)
|
102
|
-
# => { profile: { name: 'John', surname: 'Cena' } }
|
103
|
-
|
104
|
-
get_profile_jsonb(user, :by_key)
|
105
|
-
# => { name: 'John', surname: 'Cena' }
|
106
|
-
|
107
|
-
get_profile_jsonb(user, :by_block)
|
108
|
-
# => { name: 'JOHN', surname: 'CENA' }
|
109
|
-
|
110
|
-
get_profile_jsonb(user, :by_block_with_parent)
|
111
|
-
# => { name: 'JOHN', email: user.email }
|
112
|
-
```
|
data/docs/sequel.md
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
# Sequel
|
2
|
-
|
3
|
-
Under the hood, Clowne uses Sequel [`NestedAttributes` plugin](http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html) for cloning source's associations, and you need to configure it.
|
4
|
-
|
5
|
-
Example:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
class UserCloner < Clowne::Cloner
|
9
|
-
adapter :sequel
|
10
|
-
|
11
|
-
include_association :account
|
12
|
-
end
|
13
|
-
|
14
|
-
class User < Sequel::Model
|
15
|
-
# Configure NestedAttributes plugin
|
16
|
-
plugin :nested_attributes
|
17
|
-
|
18
|
-
one_to_one :account
|
19
|
-
nested_attributes :account
|
20
|
-
end
|
21
|
-
```
|
22
|
-
|
23
|
-
and get cloned user
|
24
|
-
|
25
|
-
```ruby
|
26
|
-
user = User.last
|
27
|
-
operation = UserCloner.call(user)
|
28
|
-
# => <#Clowne::Adapters::Sequel::Operation...>
|
29
|
-
cloned = operation.to_record
|
30
|
-
# => <#User id: nil, ...>
|
31
|
-
cloned.new?
|
32
|
-
# => true
|
33
|
-
```
|
34
|
-
|
35
|
-
or you can save it immediately
|
36
|
-
|
37
|
-
```ruby
|
38
|
-
user = User.last
|
39
|
-
# => <#User id: 1, ...>
|
40
|
-
operation = UserCloner.call(user)
|
41
|
-
# => <#Clowne::Adapters::Sequel::Operation...>
|
42
|
-
operation.persist
|
43
|
-
# => true
|
44
|
-
cloned = operation.to_record
|
45
|
-
# => <#User id: 2, ...>
|
46
|
-
cloned.new?
|
47
|
-
# => false
|
48
|
-
```
|
49
|
-
|
50
|
-
If you try to clone association without `NestedAttributes` plugin, Clowne will skip this declaration.
|
data/docs/supported_adapters.md
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
# Supported Adapters
|
2
|
-
|
3
|
-
Clowne supports the following ORM adapters (and associations):
|
4
|
-
|
5
|
-
Adapter |1:1 | 1:M | M:M |
|
6
|
-
---------------------------------------------------|------------|-------------|-------------------------|
|
7
|
-
[:active_record](active_record) | has_one | has_many | has_and_belongs_to_many |
|
8
|
-
[:sequel](sequel) | one_to_one | one_to_many | many_to_many |
|
9
|
-
|
10
|
-
For more information see the corresponding adapter documentation.
|