clowne 0.1.0.pre1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +15 -2
- data/CHANGELOG.md +9 -2
- data/Gemfile +1 -0
- data/README.md +25 -381
- data/clowne.gemspec +1 -0
- data/docs/.rubocop.yml +12 -0
- data/docs/active_record.md +36 -0
- data/docs/alternatives.md +26 -0
- data/docs/architecture.md +141 -0
- data/docs/basic_example.md +66 -0
- data/docs/configuration.md +29 -0
- data/docs/customization.md +64 -0
- data/docs/exclude_association.md +63 -0
- data/docs/execution_order.md +14 -0
- data/docs/finalize.md +35 -0
- data/docs/implicit_cloner.md +36 -0
- data/docs/include_association.md +119 -0
- data/docs/init_as.md +36 -0
- data/docs/inline_configuration.md +38 -0
- data/docs/installation.md +16 -0
- data/docs/nullify.md +37 -0
- data/docs/sequel.md +56 -0
- data/docs/supported_adapters.md +13 -0
- data/docs/traits.md +28 -0
- data/docs/web/.gitignore +11 -0
- data/docs/web/core/Footer.js +92 -0
- data/docs/web/i18n/en.json +134 -0
- data/docs/web/package.json +14 -0
- data/docs/web/pages/en/help.js +50 -0
- data/docs/web/pages/en/index.js +231 -0
- data/docs/web/pages/en/users.js +47 -0
- data/docs/web/sidebars.json +30 -0
- data/docs/web/siteConfig.js +44 -0
- data/docs/web/static/css/custom.css +229 -0
- data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
- data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
- data/docs/web/static/fonts/StemText.woff +0 -0
- data/docs/web/static/fonts/StemTextBold.woff +0 -0
- data/docs/web/static/img/favicon/favicon.ico +0 -0
- data/docs/web/yarn.lock +1741 -0
- data/gemfiles/activerecord42.gemfile +1 -0
- data/gemfiles/jruby.gemfile +2 -0
- data/gemfiles/railsmaster.gemfile +1 -0
- data/lib/clowne.rb +3 -1
- data/lib/clowne/adapters/active_record.rb +3 -12
- data/lib/clowne/adapters/active_record/association.rb +1 -1
- data/lib/clowne/adapters/active_record/associations/base.rb +8 -48
- data/lib/clowne/adapters/active_record/associations/has_one.rb +8 -1
- data/lib/clowne/adapters/active_record/associations/noop.rb +4 -1
- data/lib/clowne/adapters/active_record/dsl.rb +33 -0
- data/lib/clowne/adapters/base.rb +13 -6
- data/lib/clowne/adapters/base/association.rb +69 -0
- data/lib/clowne/adapters/base/finalize.rb +1 -1
- data/lib/clowne/adapters/base/init_as.rb +21 -0
- data/lib/clowne/adapters/registry.rb +5 -11
- data/lib/clowne/adapters/sequel.rb +25 -0
- data/lib/clowne/adapters/sequel/association.rb +47 -0
- data/lib/clowne/adapters/sequel/associations.rb +26 -0
- data/lib/clowne/adapters/sequel/associations/base.rb +23 -0
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +19 -0
- data/lib/clowne/adapters/sequel/associations/noop.rb +16 -0
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +23 -0
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +23 -0
- data/lib/clowne/adapters/sequel/copier.rb +23 -0
- data/lib/clowne/adapters/sequel/record_wrapper.rb +59 -0
- data/lib/clowne/cloner.rb +6 -4
- data/lib/clowne/declarations.rb +1 -0
- data/lib/clowne/declarations/exclude_association.rb +0 -5
- data/lib/clowne/declarations/include_association.rb +0 -2
- data/lib/clowne/declarations/init_as.rb +20 -0
- data/lib/clowne/declarations/trait.rb +2 -0
- data/lib/clowne/ext/orm_ext.rb +21 -0
- data/lib/clowne/ext/string_constantize.rb +2 -2
- data/lib/clowne/planner.rb +11 -4
- data/lib/clowne/version.rb +1 -1
- metadata +70 -4
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
id: execution_order
|
3
|
+
title: Execution Order
|
4
|
+
---
|
5
|
+
|
6
|
+
The order of cloning actions depends on the adapter (i.e., could be customized).
|
7
|
+
|
8
|
+
All built-in adapters have the same order:
|
9
|
+
- init clone (see [`init_as`](init_as.md))
|
10
|
+
- clone associations
|
11
|
+
- nullify attributes
|
12
|
+
- run [`finalize`](finalize.md) blocks
|
13
|
+
|
14
|
+
The order of [`finalize`](finalize.md) blocks is the order they've been written.
|
data/docs/finalize.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
---
|
2
|
+
id: finalize
|
3
|
+
title: Finalization
|
4
|
+
sidebar_label: Finalize
|
5
|
+
---
|
6
|
+
|
7
|
+
To apply custom transformations to the cloned record, you can use the `finalize` declaration:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class UserCloner < Clowne::Cloner
|
11
|
+
finalize do |_source, record, _params|
|
12
|
+
record.name = 'This is copy!'
|
13
|
+
end
|
14
|
+
|
15
|
+
trait :change_email do
|
16
|
+
finalize do |_source, record, params|
|
17
|
+
record.email = params[:email]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
clone = UserCloner.call(user)
|
23
|
+
clone.name
|
24
|
+
# => 'This is copy!'
|
25
|
+
clone.email == 'clone@example.com'
|
26
|
+
# => false
|
27
|
+
|
28
|
+
clone2 = UserCloner.call(user, traits: :change_email)
|
29
|
+
clone2.name
|
30
|
+
# => 'This is copy!'
|
31
|
+
clone2.email
|
32
|
+
# => 'clone@example.com'
|
33
|
+
```
|
34
|
+
|
35
|
+
Finalization blocks are called at the end of the [cloning process](execution_order.md).
|
@@ -0,0 +1,36 @@
|
|
1
|
+
---
|
2
|
+
id: implicit_cloner
|
3
|
+
title: Implicit Cloner
|
4
|
+
---
|
5
|
+
|
6
|
+
When [cloning associations](include_association.md) Clowne tries to infer an appropriate cloner class for the records (unless `clone_with` specified).
|
7
|
+
|
8
|
+
It relies on the naming convention: `MyModel` -> `MyModelCloner`.
|
9
|
+
|
10
|
+
Consider an example:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
has_one :profile
|
15
|
+
end
|
16
|
+
|
17
|
+
class UserCloner < Clowne::Cloner
|
18
|
+
include_association :profile
|
19
|
+
end
|
20
|
+
|
21
|
+
class ProfileCloner < Clowne::Cloner
|
22
|
+
finalize do |source, record|
|
23
|
+
record.name = "Clone of #{source.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
user = User.last
|
28
|
+
user.profile.name
|
29
|
+
#=> "Bimbo"
|
30
|
+
|
31
|
+
cloned = UserCloner.call(user)
|
32
|
+
cloned.profile.name
|
33
|
+
# => "Clone of Bimbo"
|
34
|
+
```
|
35
|
+
|
36
|
+
**NOTE:** when using [in-model cloner](active_record.md) for ActiveRecord it is used by default.
|
@@ -0,0 +1,119 @@
|
|
1
|
+
---
|
2
|
+
id: include_association
|
3
|
+
title: Include Association
|
4
|
+
---
|
5
|
+
|
6
|
+
Use this declaration to clone model's associations:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
has_one :profile
|
11
|
+
end
|
12
|
+
|
13
|
+
class UserCloner < Clowne::Cloner
|
14
|
+
include_association :profile
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Looks pretty simple, right? But that's not all we may offer you! :)
|
19
|
+
|
20
|
+
The declaration supports additional arguments:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
include_association name, scope, options
|
24
|
+
```
|
25
|
+
|
26
|
+
## Scope
|
27
|
+
|
28
|
+
Scope can be a:
|
29
|
+
- `Symbol` - named scope
|
30
|
+
- `Proc` - custom scope (supports parameters).
|
31
|
+
|
32
|
+
Example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
has_many :accounts
|
37
|
+
has_many :posts
|
38
|
+
end
|
39
|
+
|
40
|
+
class Account < ActiveRecord::Base
|
41
|
+
scope :active, -> { where(active: true) }
|
42
|
+
end
|
43
|
+
|
44
|
+
class Post < ActiveRecord::Base
|
45
|
+
# t.string :status
|
46
|
+
end
|
47
|
+
|
48
|
+
class UserCloner < Clowne::Cloner
|
49
|
+
include_association :accounts, :active
|
50
|
+
include_association :posts, ->(params) { where(state: params[:status]) if params[:status] }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clone only draft posts
|
54
|
+
UserCloner.call(user, status: :draft)
|
55
|
+
# => <#User...
|
56
|
+
```
|
57
|
+
|
58
|
+
## Options
|
59
|
+
|
60
|
+
The following options are available:
|
61
|
+
- `:clone_with` - use custom cloner\*
|
62
|
+
- `:traits` - define special traits.
|
63
|
+
|
64
|
+
\* **NOTE:** the same cloner class would be used for **all children**
|
65
|
+
|
66
|
+
Example:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class User < ActiveRecord::Base
|
70
|
+
has_many :posts
|
71
|
+
end
|
72
|
+
|
73
|
+
class Post < ActiveRecord::Base
|
74
|
+
# t.string :title
|
75
|
+
has_many :tags
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class PostSpecialCloner < Clowne::Cloner
|
81
|
+
nullify :title
|
82
|
+
|
83
|
+
trait :with_tags do
|
84
|
+
include_association :tags
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class UserCloner < Clowne::Cloner
|
89
|
+
adapter :active_record
|
90
|
+
|
91
|
+
include_association :posts, clone_with: PostSpecialCloner
|
92
|
+
# or clone user's posts with tags!
|
93
|
+
# include_association :posts, clone_with: PostSpecialCloner, traits: :with_tags
|
94
|
+
end
|
95
|
+
|
96
|
+
UserCloner.call(user)
|
97
|
+
# => <#User...
|
98
|
+
```
|
99
|
+
|
100
|
+
**NOTE**: if custom cloner is not defined, Clowne tries to infer the [implicit cloner](implicit_cloner.md).
|
101
|
+
|
102
|
+
## Include multiple associations
|
103
|
+
|
104
|
+
You can include multiple associations at once too:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class User < ActiveRecord::Base
|
108
|
+
has_many :accounts
|
109
|
+
has_many :posts
|
110
|
+
end
|
111
|
+
|
112
|
+
class UserCloner < Clowne::Cloner
|
113
|
+
adapter :active_record
|
114
|
+
|
115
|
+
include_associations :accounts, :posts
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
**NOTE:** in that case, it's not possible to provide custom scopes and options.
|
data/docs/init_as.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
---
|
2
|
+
id: init_as
|
3
|
+
title: Initialize Cloning Target
|
4
|
+
sidebar_label: Init As
|
5
|
+
---
|
6
|
+
|
7
|
+
You can override the default Clowne method which generates a _plain_ copy of a source object.
|
8
|
+
By default, Clowne initiates the cloned record using a `#dup` method.
|
9
|
+
|
10
|
+
For example, Cloners could be used not only to generate _fresh_ new models but to apply some transformations to the existing record:
|
11
|
+
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class User < ApplicationRecord
|
15
|
+
has_one :profile
|
16
|
+
has_many :posts
|
17
|
+
end
|
18
|
+
|
19
|
+
class UserCloner < Clowne::Cloner
|
20
|
+
adapter :active_record
|
21
|
+
|
22
|
+
include_association :profile
|
23
|
+
|
24
|
+
trait :copy_settings do
|
25
|
+
# Use a `target` for all the actions
|
26
|
+
init_as { |_source, target:| target }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
jack = User.find_by(email: 'jack@evl.ms')
|
31
|
+
john = User.find_by(email: 'john@evl.ms')
|
32
|
+
|
33
|
+
# we want to clone Jack's profile settings to another user,
|
34
|
+
# without creating a new one
|
35
|
+
UserCloner.call(jack, traits: :copy_settings, target: john)
|
36
|
+
```
|
@@ -0,0 +1,38 @@
|
|
1
|
+
---
|
2
|
+
id: inline_configuration
|
3
|
+
title: Inline Configuration
|
4
|
+
---
|
5
|
+
|
6
|
+
You can also enhance the cloner configuration inline (i.e., add declarations dynamically):
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
cloned = UserCloner.call(User.last) do
|
10
|
+
exclude_association :profile
|
11
|
+
|
12
|
+
finalize do |source, record|
|
13
|
+
record.email = "clone_of_#{source.email}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
cloned.email
|
18
|
+
# => "clone_of_john@example.com"
|
19
|
+
|
20
|
+
# associations:
|
21
|
+
cloned.posts.size == User.last.posts.size
|
22
|
+
# => true
|
23
|
+
cloned.profile
|
24
|
+
# => nil
|
25
|
+
```
|
26
|
+
|
27
|
+
Inline enhancement doesn't affect the _global_ configuration so that you can use it without any fear.
|
28
|
+
|
29
|
+
Thus it's also possible to clone objects without any cloner classes at all by using `Clowne::Cloner`:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
cloned = Clowne::Cloner.call(user) do
|
33
|
+
# anything you want!
|
34
|
+
end
|
35
|
+
|
36
|
+
cloned
|
37
|
+
# => <#User..
|
38
|
+
```
|
data/docs/nullify.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
id: nullify
|
3
|
+
title: Nullify Attributes
|
4
|
+
sidebar_label: Nullify
|
5
|
+
---
|
6
|
+
|
7
|
+
To set a bunch of attributes to `nil` you can use the `nullify` declaration:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class User < ActiveRecord::Base
|
11
|
+
# t.string :name
|
12
|
+
# t.string :surname
|
13
|
+
# t.string :email
|
14
|
+
end
|
15
|
+
|
16
|
+
class UserCloner < Clowne::Cloner
|
17
|
+
nullify :name, :email
|
18
|
+
|
19
|
+
trait :nullify_surname do
|
20
|
+
nullify :surname
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
clone = UserCloner.call(user)
|
25
|
+
clone.name.nil?
|
26
|
+
# => true
|
27
|
+
clone.email.nil?
|
28
|
+
# => true
|
29
|
+
clone.surname.nil?
|
30
|
+
# => false
|
31
|
+
|
32
|
+
clone2 = UserCloner.call(user, traits: :nullify_surname)
|
33
|
+
clone2.name.nil?
|
34
|
+
# => true
|
35
|
+
clone2.surname.nil?
|
36
|
+
# => true
|
37
|
+
```
|
data/docs/sequel.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
---
|
2
|
+
id: sequel
|
3
|
+
title: Sequel
|
4
|
+
---
|
5
|
+
|
6
|
+
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
|
+
|
8
|
+
Also, Sequel target record wrapped into a special class for implementation full Clowne's behavior. You need to use method `to_model` for getting final cloned `Sequel::Model` object (or you can use `save` for saving the cloned object to DB).
|
9
|
+
|
10
|
+
Example:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class UserCloner < Clowne::Cloner
|
14
|
+
adapter :sequel
|
15
|
+
|
16
|
+
include_association :account
|
17
|
+
end
|
18
|
+
|
19
|
+
class User < Sequel::Model
|
20
|
+
# Configure NestedAttributes plugin
|
21
|
+
plugin :nested_attributes
|
22
|
+
|
23
|
+
one_to_one :account
|
24
|
+
nested_attributes :account
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
and get cloned user
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
user = User.last
|
32
|
+
wrapper = UserCloner.call(user)
|
33
|
+
wrapper.class
|
34
|
+
# => Clowne::Adapters::Sequel::RecordWrapper
|
35
|
+
cloned_record = wrapper.to_model
|
36
|
+
cloned_record.class
|
37
|
+
# => User
|
38
|
+
cloned_record.new?
|
39
|
+
# => true
|
40
|
+
```
|
41
|
+
|
42
|
+
or you can save it immediately
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
user = User.last
|
46
|
+
wrapper = UserCloner.call(user)
|
47
|
+
wrapper.class
|
48
|
+
# => Clowne::Adapters::Sequel::RecordWrapper
|
49
|
+
cloned_record = wrapper.save
|
50
|
+
cloned_record.class
|
51
|
+
# => User
|
52
|
+
cloned_record.new?
|
53
|
+
# => false
|
54
|
+
```
|
55
|
+
|
56
|
+
If you try to clone associations without `NestedAttributes` plugin, Clowne will skip this declaration.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
id: supported_adapters
|
3
|
+
title: Supported Adapters
|
4
|
+
---
|
5
|
+
|
6
|
+
Clowne supports the following ORM adapters (and associations):
|
7
|
+
|
8
|
+
Adapter |1:1 | 1:M | M:M |
|
9
|
+
---------------------------------------------------|------------|-------------|-------------------------|
|
10
|
+
[:active_record](/clowne/docs/active_record.html) | has_one | has_many | has_and_belongs_to_many |
|
11
|
+
[:sequel](/clowne/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
|
12
|
+
|
13
|
+
For more information see the corresponding adapter documentation.
|
data/docs/traits.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
---
|
2
|
+
id: traits
|
3
|
+
title: Traits
|
4
|
+
---
|
5
|
+
|
6
|
+
Traits allow you to group cloner declarations together and then apply them (like in [`factory_bot`](https://github.com/thoughtbot/factory_bot)):
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class UserCloner < Clowne::Cloner
|
10
|
+
trait :with_posts do
|
11
|
+
include_association :posts
|
12
|
+
end
|
13
|
+
|
14
|
+
trait :with_profile do
|
15
|
+
include_association :profile
|
16
|
+
end
|
17
|
+
|
18
|
+
trait :nullify_name do
|
19
|
+
nullify :name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
UserCloner.call(user, traits: %i[with_posts with_profile nullify_name])
|
24
|
+
# or
|
25
|
+
UserCloner.call(user, traits: :nullify_name)
|
26
|
+
# or
|
27
|
+
# ...
|
28
|
+
```
|