clowne 0.1.0.pre1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|
+
```
|