clowne 1.0.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec-jruby.yml +33 -0
- data/.github/workflows/rspec-truffle.yml +35 -0
- data/.github/workflows/rspec.yml +51 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.rubocop.yml +13 -52
- data/CHANGELOG.md +23 -0
- data/Gemfile +9 -9
- data/README.md +10 -10
- data/Rakefile +3 -3
- data/clowne.gemspec +15 -9
- data/docs/.nojekyll +0 -0
- data/docs/.rubocop.yml +5 -2
- data/docs/CNAME +1 -0
- data/docs/README.md +131 -0
- data/docs/_sidebar.md +25 -0
- data/docs/active_record.md +2 -5
- data/docs/after_clone.md +53 -0
- data/docs/after_persist.md +9 -12
- data/docs/architecture.md +2 -5
- data/docs/assets/docsify.min.js +1 -0
- data/docs/assets/prism-ruby.min.js +1 -0
- data/docs/assets/styles.css +348 -0
- data/docs/assets/vue.css +1 -0
- data/docs/clone_mapper.md +3 -6
- data/docs/customization.md +1 -4
- data/docs/exclude_association.md +1 -4
- data/docs/finalize.md +6 -10
- data/docs/from_v02_to_v1.md +2 -10
- data/docs/getting_started.md +171 -0
- data/docs/implicit_cloner.md +1 -4
- data/docs/include_association.md +14 -4
- data/docs/index.html +29 -0
- data/docs/init_as.md +4 -8
- data/docs/inline_configuration.md +1 -4
- data/docs/nullify.md +1 -5
- data/docs/operation.md +4 -7
- data/docs/parameters.md +7 -10
- data/docs/sequel.md +1 -4
- data/docs/supported_adapters.md +3 -6
- data/docs/testing.md +18 -21
- data/docs/traits.md +1 -4
- data/gemfiles/activerecord42.gemfile +5 -5
- data/gemfiles/jruby.gemfile +6 -6
- data/gemfiles/railsmaster.gemfile +6 -6
- data/lib/clowne/adapters/active_record/associations/base.rb +1 -1
- data/lib/clowne/adapters/active_record/associations/belongs_to.rb +28 -0
- data/lib/clowne/adapters/active_record/associations/has_one.rb +1 -2
- data/lib/clowne/adapters/active_record/associations.rb +7 -5
- data/lib/clowne/adapters/active_record/dsl.rb +2 -2
- data/lib/clowne/adapters/active_record/resolvers/association.rb +1 -2
- data/lib/clowne/adapters/active_record.rb +3 -3
- data/lib/clowne/adapters/base/association.rb +1 -1
- data/lib/clowne/adapters/base.rb +14 -8
- data/lib/clowne/adapters/sequel/associations/base.rb +2 -2
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +4 -4
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +1 -1
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +1 -1
- data/lib/clowne/adapters/sequel/associations.rb +5 -5
- data/lib/clowne/adapters/sequel/operation.rb +6 -3
- data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +1 -1
- data/lib/clowne/adapters/sequel/resolvers/association.rb +1 -2
- data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +1 -1
- data/lib/clowne/adapters/sequel.rb +7 -7
- data/lib/clowne/cloner.rb +11 -11
- data/lib/clowne/declarations/after_clone.rb +21 -0
- data/lib/clowne/declarations/after_persist.rb +3 -3
- data/lib/clowne/declarations/exclude_association.rb +1 -1
- data/lib/clowne/declarations/finalize.rb +3 -3
- data/lib/clowne/declarations/include_association.rb +1 -1
- data/lib/clowne/declarations/init_as.rb +3 -3
- data/lib/clowne/declarations/nullify.rb +2 -2
- data/lib/clowne/declarations/trait.rb +1 -1
- data/lib/clowne/declarations.rb +15 -14
- data/lib/clowne/ext/orm_ext.rb +1 -1
- data/lib/clowne/ext/record_key.rb +1 -1
- data/lib/clowne/ext/string_constantize.rb +1 -1
- data/lib/clowne/ext/yield_self_then.rb +2 -2
- data/lib/clowne/planner.rb +1 -1
- data/lib/clowne/resolvers/after_clone.rb +18 -0
- data/lib/clowne/resolvers/after_persist.rb +1 -1
- data/lib/clowne/resolvers/finalize.rb +1 -1
- data/lib/clowne/resolvers/init_as.rb +0 -1
- data/lib/clowne/rspec/clone_association.rb +3 -4
- data/lib/clowne/rspec/clone_associations.rb +2 -2
- data/lib/clowne/rspec/helpers.rb +1 -1
- data/lib/clowne/rspec.rb +3 -3
- data/lib/clowne/utils/clone_mapper.rb +1 -1
- data/lib/clowne/utils/operation.rb +19 -7
- data/lib/clowne/utils/params.rb +1 -1
- data/lib/clowne/version.rb +1 -1
- data/lib/clowne.rb +10 -11
- metadata +60 -38
- data/.travis.yml +0 -55
- data/docs/alternatives.md +0 -26
- data/docs/basic_example.md +0 -83
- data/docs/installation.md +0 -46
- data/docs/overview.md +0 -24
- data/docs/web/.gitignore +0 -11
- data/docs/web/README.md +0 -6
- data/docs/web/core/Footer.js +0 -88
- data/docs/web/i18n/en.json +0 -140
- data/docs/web/package.json +0 -14
- data/docs/web/pages/en/help.js +0 -50
- data/docs/web/pages/en/index.js +0 -231
- data/docs/web/pages/en/users.js +0 -47
- data/docs/web/sidebars.json +0 -37
- data/docs/web/siteConfig.js +0 -46
- data/docs/web/static/css/custom.css +0 -235
- data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
- data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
- data/docs/web/static/img/favicon/favicon.ico +0 -0
- data/docs/web/yarn.lock +0 -1741
data/docs/customization.md
CHANGED
data/docs/exclude_association.md
CHANGED
data/docs/finalize.md
CHANGED
@@ -1,19 +1,15 @@
|
|
1
|
-
|
2
|
-
id: finalize
|
3
|
-
title: Finalization
|
4
|
-
sidebar_label: Finalize
|
5
|
-
---
|
1
|
+
# Finalization
|
6
2
|
|
7
3
|
To apply custom transformations to the cloned record, you can use the `finalize` declaration:
|
8
4
|
|
9
5
|
```ruby
|
10
6
|
class UserCloner < Clowne::Cloner
|
11
|
-
finalize do |_source, record, _params|
|
12
|
-
record.name =
|
7
|
+
finalize do |_source, record, **_params|
|
8
|
+
record.name = "This is copy!"
|
13
9
|
end
|
14
10
|
|
15
11
|
trait :change_email do
|
16
|
-
finalize do |_source, record, params|
|
12
|
+
finalize do |_source, record, **params|
|
17
13
|
record.email = params[:email]
|
18
14
|
end
|
19
15
|
end
|
@@ -22,7 +18,7 @@ end
|
|
22
18
|
cloned = UserCloner.call(user).to_record
|
23
19
|
cloned.name
|
24
20
|
# => 'This is copy!'
|
25
|
-
cloned.email ==
|
21
|
+
cloned.email == "clone@example.com"
|
26
22
|
# => false
|
27
23
|
|
28
24
|
cloned2 = UserCloner.call(user, traits: :change_email).to_record
|
@@ -32,4 +28,4 @@ cloned2.email
|
|
32
28
|
# => 'clone@example.com'
|
33
29
|
```
|
34
30
|
|
35
|
-
Finalization blocks are called at the end of the [cloning process](
|
31
|
+
Finalization blocks are called at the end of the [cloning process](getting_started?id=execution-order).
|
data/docs/from_v02_to_v1.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: from_v02_to_v10
|
3
|
-
title: From v0.2.x to v1.0.0
|
4
|
-
---
|
1
|
+
# From v0.2.x to v1.0.0
|
5
2
|
|
6
3
|
The breaking change of v1.0 is the return of a unified [`result object`](operation.md) for all adapters.
|
7
4
|
|
@@ -37,9 +34,8 @@ clone.persisted?
|
|
37
34
|
|
38
35
|
### Move post-processing cloning logic into [`after_persist`](after_persist.md) callback (if you have it)
|
39
36
|
|
40
|
-
|
37
|
+
*Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter.*
|
41
38
|
|
42
|
-
<span style="display:none;"># rubocop:disable all</span>
|
43
39
|
```ruby
|
44
40
|
# Before
|
45
41
|
clone = UserCloner.call(user)
|
@@ -56,8 +52,6 @@ end
|
|
56
52
|
|
57
53
|
clone = UserCloner.call(user).tap(&:persist).to_record
|
58
54
|
```
|
59
|
-
<span style="display:none;"># rubocop:enable all</span>
|
60
|
-
|
61
55
|
## Sequel
|
62
56
|
|
63
57
|
### Use `to_record` instead of `to_model`
|
@@ -78,7 +72,6 @@ clone.new?
|
|
78
72
|
|
79
73
|
### Use `operation#persist` instead of converting to model and calling `#save`
|
80
74
|
|
81
|
-
<span style="display:none;"># rubocop:disable all</span>
|
82
75
|
```ruby
|
83
76
|
# Before
|
84
77
|
record_wrapper = UserCloner.call(user)
|
@@ -88,4 +81,3 @@ clone.save
|
|
88
81
|
# After
|
89
82
|
clone = UserCloner.call(user).tap(&:persist).to_record
|
90
83
|
```
|
91
|
-
<span style="display:none;"># rubocop:enable all</span>
|
@@ -0,0 +1,171 @@
|
|
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
CHANGED
data/docs/include_association.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: include_association
|
3
|
-
title: Include Association
|
4
|
-
---
|
1
|
+
# Include Association
|
5
2
|
|
6
3
|
Use this declaration to clone model's associations:
|
7
4
|
|
@@ -23,6 +20,13 @@ The declaration supports additional arguments:
|
|
23
20
|
include_association name, scope, options
|
24
21
|
```
|
25
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
|
+
|
26
30
|
## Scope
|
27
31
|
|
28
32
|
Scope can be a:
|
@@ -121,3 +125,9 @@ end
|
|
121
125
|
```
|
122
126
|
|
123
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
ADDED
@@ -0,0 +1,29 @@
|
|
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
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
|
2
|
-
id: init_as
|
3
|
-
title: Initialize Cloning Target
|
4
|
-
sidebar_label: Init As
|
5
|
-
---
|
1
|
+
# Initialize Cloning Target
|
6
2
|
|
7
3
|
You can override the default Clowne method which generates a _plain_ copy of a source object.
|
8
4
|
By default, Clowne initiates the cloned record using a `#dup` method.
|
@@ -27,12 +23,12 @@ class UserCloner < Clowne::Cloner
|
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
30
|
-
jack = User.find_by(email:
|
26
|
+
jack = User.find_by(email: "jack@evl.ms")
|
31
27
|
# => <#User id: 1, ...>
|
32
|
-
jack.create_profile(name:
|
28
|
+
jack.create_profile(name: "Jack")
|
33
29
|
# => <#Profile id: 1, name: 'Jack', ...>
|
34
30
|
|
35
|
-
john = User.find_by(email:
|
31
|
+
john = User.find_by(email: "john@evl.ms")
|
36
32
|
# => <#User id: 2, ...>
|
37
33
|
|
38
34
|
# we want to clone Jack's profile to John's user,
|
data/docs/nullify.md
CHANGED
data/docs/operation.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: operation
|
3
|
-
title: Operation
|
4
|
-
---
|
1
|
+
# Operation
|
5
2
|
|
6
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`.
|
7
4
|
|
@@ -14,11 +11,11 @@ class UserCloner < Clowne::Cloner
|
|
14
11
|
nullify :email
|
15
12
|
|
16
13
|
after_persist do |_origin, cloned, **|
|
17
|
-
cloned.
|
14
|
+
cloned.update(email: "evl-#{cloned.id}.ms")
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
|
-
user = User.create(email:
|
18
|
+
user = User.create(email: "evl.ms")
|
22
19
|
# => <#User id: 1, email: 'evl.ms', ...>
|
23
20
|
|
24
21
|
operation = UserCloner.call(user)
|
@@ -37,7 +34,7 @@ operation.to_record
|
|
37
34
|
# Call only after_persist callbacks:
|
38
35
|
user2 = operation.to_record
|
39
36
|
# => <#User id: 2, email: 'evl-2.ms', ...>
|
40
|
-
user2.
|
37
|
+
user2.update(email: "admin@example.com")
|
41
38
|
# => <#User id: 2, email: 'admin@example.com' ...>
|
42
39
|
operation.run_after_persist
|
43
40
|
# => <#User id: 2, email: 'evl-2.ms', ...>
|
data/docs/parameters.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: parameters
|
3
|
-
title: Parameters
|
4
|
-
---
|
1
|
+
# Parameters
|
5
2
|
|
6
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.
|
7
4
|
|
@@ -11,12 +8,12 @@ Example:
|
|
11
8
|
class UserCloner < Clowne::Cloner
|
12
9
|
include_association :posts, ->(params) { where(state: params[:state]) }
|
13
10
|
|
14
|
-
finalize do |_source, record, params|
|
11
|
+
finalize do |_source, record, **params|
|
15
12
|
record.email = params[:email]
|
16
13
|
end
|
17
14
|
end
|
18
15
|
|
19
|
-
operation = UserCloner.call(user, state: :draft, email:
|
16
|
+
operation = UserCloner.call(user, state: :draft, email: "cloned@example.com")
|
20
17
|
cloned = operation.to_record
|
21
18
|
cloned.email
|
22
19
|
# => 'cloned@example.com'
|
@@ -30,7 +27,7 @@ As result we strongly recommend to use ruby keyword arguments instead of params
|
|
30
27
|
|
31
28
|
```ruby
|
32
29
|
# Bad
|
33
|
-
finalize do |_source, record, params|
|
30
|
+
finalize do |_source, record, **params|
|
34
31
|
record.email = params[:email]
|
35
32
|
end
|
36
33
|
|
@@ -42,7 +39,7 @@ end
|
|
42
39
|
|
43
40
|
## Nested Parameters
|
44
41
|
|
45
|
-
Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/
|
42
|
+
Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/clowne-rb/clowne/issues/15)).
|
46
43
|
|
47
44
|
Let's explain what the difference:
|
48
45
|
|
@@ -85,7 +82,7 @@ class UserCloner < Clowne::Cloner
|
|
85
82
|
end
|
86
83
|
|
87
84
|
class ProfileCloner < Clowne::Cloner
|
88
|
-
finalize do |_source, record, params|
|
85
|
+
finalize do |_source, record, **params|
|
89
86
|
record.jsonb_field = params
|
90
87
|
end
|
91
88
|
end
|
@@ -93,7 +90,7 @@ end
|
|
93
90
|
# Execute:
|
94
91
|
|
95
92
|
def get_profile_jsonb(user, trait)
|
96
|
-
params = {
|
93
|
+
params = {profile: {name: "John", surname: "Cena"}}
|
97
94
|
cloned = UserCloner.call(user, traits: trait, **params).to_record
|
98
95
|
cloned.profile.jsonb_field
|
99
96
|
end
|
data/docs/sequel.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: sequel
|
3
|
-
title: Sequel
|
4
|
-
---
|
1
|
+
# Sequel
|
5
2
|
|
6
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.
|
7
4
|
|
data/docs/supported_adapters.md
CHANGED
@@ -1,13 +1,10 @@
|
|
1
|
-
|
2
|
-
id: supported_adapters
|
3
|
-
title: Supported Adapters
|
4
|
-
---
|
1
|
+
# Supported Adapters
|
5
2
|
|
6
3
|
Clowne supports the following ORM adapters (and associations):
|
7
4
|
|
8
5
|
Adapter |1:1 | 1:M | M:M |
|
9
6
|
---------------------------------------------------|------------|-------------|-------------------------|
|
10
|
-
[:active_record](
|
11
|
-
[:sequel](
|
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 |
|
12
9
|
|
13
10
|
For more information see the corresponding adapter documentation.
|
data/docs/testing.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: testing
|
3
|
-
title: Testing
|
4
|
-
---
|
1
|
+
# Testing
|
5
2
|
|
6
3
|
Clowne provides specific tools to help you test your cloners.
|
7
4
|
|
@@ -52,7 +49,7 @@ class UserCloner < Clowne::Cloner
|
|
52
49
|
|
53
50
|
trait :with_popular_posts do
|
54
51
|
include_association :posts, (lambda do |params|
|
55
|
-
where(
|
52
|
+
where("rating > ?", params[:min_rating])
|
56
53
|
end)
|
57
54
|
end
|
58
55
|
end
|
@@ -62,7 +59,7 @@ class PostCloner < Clowne::Cloner
|
|
62
59
|
include_association :comments
|
63
60
|
|
64
61
|
trait :mark_as_copy do |_, record|
|
65
|
-
record.title +=
|
62
|
+
record.title += " (copy)"
|
66
63
|
end
|
67
64
|
end
|
68
65
|
```
|
@@ -74,7 +71,7 @@ Currently, only [RSpec](http://rspec.info/) is supported.
|
|
74
71
|
Add this line to your `spec_helper.rb` (or `rails_helper.rb`):
|
75
72
|
|
76
73
|
```ruby
|
77
|
-
require
|
74
|
+
require "clowne/rspec"
|
78
75
|
```
|
79
76
|
|
80
77
|
## Configuration matchers
|
@@ -151,47 +148,47 @@ Most of the time these actions don't depend on each other, thus we can test them
|
|
151
148
|
```ruby
|
152
149
|
# spec/cloners/user_cloner_spec.rb
|
153
150
|
RSpec.describe UserCloner, type: :cloner do
|
154
|
-
subject(:user) { create :user, name:
|
151
|
+
subject(:user) { create :user, name: "Bombon" }
|
155
152
|
|
156
|
-
specify
|
153
|
+
specify "simple case" do
|
157
154
|
# apply only the specified part of the plan
|
158
155
|
cloned_user = described_class.partial_apply(:nullify, user).to_record
|
159
156
|
expect(cloned_user.email).to be_nil
|
160
157
|
# finalize wasn't applied
|
161
|
-
expect(cloned_user.name).to eq
|
158
|
+
expect(cloned_user.name).to eq "Bombon"
|
162
159
|
end
|
163
160
|
|
164
|
-
specify
|
165
|
-
cloned_user = described_class.partial_apply(:finalize, user, name:
|
161
|
+
specify "with params" do
|
162
|
+
cloned_user = described_class.partial_apply(:finalize, user, name: "new name").to_record
|
166
163
|
# nullify actions were not applied!
|
167
164
|
expect(cloned_user.email).to eq user.email
|
168
165
|
# finalize was applied
|
169
|
-
expect(cloned_user.name).to eq
|
166
|
+
expect(cloned_user.name).to eq "new name"
|
170
167
|
end
|
171
168
|
|
172
|
-
specify
|
173
|
-
a_user = create(:user, name:
|
169
|
+
specify "with traits" do
|
170
|
+
a_user = create(:user, name: "Dindon")
|
174
171
|
cloned_user = described_class.partial_apply(
|
175
172
|
:init_as, user, traits: :copy, target: a_user
|
176
173
|
).to_record
|
177
174
|
# returned user is the same as target
|
178
175
|
expect(cloned_user).to be_eql(a_user)
|
179
|
-
expect(cloned_user.name).to eq
|
176
|
+
expect(cloned_user.name).to eq "Bombon"
|
180
177
|
end
|
181
178
|
|
182
|
-
specify
|
183
|
-
create(:post, user: user, rating: 1, text:
|
184
|
-
create(:post, user: user, rating: 2, text:
|
179
|
+
specify "associations" do
|
180
|
+
create(:post, user: user, rating: 1, text: "Boom Boom")
|
181
|
+
create(:post, user: user, rating: 2, text: "Flying Dumplings")
|
185
182
|
|
186
183
|
# you can specify which associations to include (you can use array)
|
187
184
|
# to apply all associations write:
|
188
185
|
# plan.apply(:association)
|
189
186
|
cloned_user = described_class.partial_apply(
|
190
|
-
|
187
|
+
"association.posts", user, traits: :with_popular_posts, min_rating: 1
|
191
188
|
).to_record
|
192
189
|
|
193
190
|
expect(cloned_user.posts.size).to eq 1
|
194
|
-
expect(cloned_user.posts.first.text).to eq
|
191
|
+
expect(cloned_user.posts.first.text).to eq "Flying Dumplings"
|
195
192
|
end
|
196
193
|
end
|
197
194
|
```
|
data/docs/traits.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
5
|
+
gem "activerecord", "~> 4.2"
|
6
|
+
gem "sequel", ">= 5.0"
|
7
|
+
gem "sqlite3", "~> 1.3.13"
|
8
8
|
|
9
|
-
gemspec path:
|
9
|
+
gemspec path: ".."
|
data/gemfiles/jruby.gemfile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "activerecord-jdbcsqlite3-adapter", "~> 50.0"
|
6
|
+
gem "jdbc-sqlite3"
|
7
|
+
gem "activerecord", "~> 5.0.0"
|
8
|
+
gem "sequel", ">= 5.0"
|
9
9
|
|
10
|
-
gemspec path:
|
10
|
+
gemspec path: ".."
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "arel", github: "rails/arel"
|
6
|
+
gem "rails", github: "rails/rails"
|
7
|
+
gem "sequel", github: "jeremyevans/sequel"
|
8
|
+
gem "sqlite3"
|
9
9
|
|
10
|
-
gemspec path:
|
10
|
+
gemspec path: ".."
|