clowne 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.travis.yml +6 -3
- data/CHANGELOG.md +14 -0
- data/Gemfile +1 -1
- data/README.md +30 -9
- data/clowne.gemspec +4 -3
- data/docs/active_record.md +2 -2
- data/docs/after_persist.md +80 -0
- data/docs/basic_example.md +22 -5
- data/docs/clone_mapper.md +62 -0
- data/docs/customization.md +2 -1
- data/docs/exclude_association.md +5 -4
- data/docs/finalize.md +2 -2
- data/docs/from_v02_to_v1.md +91 -0
- data/docs/implicit_cloner.md +1 -1
- data/docs/include_association.md +4 -4
- data/docs/init_as.md +10 -2
- data/docs/inline_configuration.md +4 -2
- data/docs/installation.md +31 -1
- data/docs/nullify.md +2 -2
- data/docs/operation.md +58 -0
- data/docs/overview.md +24 -0
- data/docs/parameters.md +5 -4
- data/docs/sequel.md +15 -18
- data/docs/supported_adapters.md +2 -2
- data/docs/testing.md +7 -5
- data/docs/web/README.md +6 -0
- data/docs/web/core/Footer.js +5 -9
- data/docs/web/i18n/en.json +8 -4
- data/docs/web/pages/en/index.js +1 -1
- data/docs/web/sidebars.json +10 -4
- data/docs/web/siteConfig.js +6 -4
- data/docs/web/static/css/custom.css +16 -10
- data/gemfiles/activerecord42.gemfile +3 -1
- data/gemfiles/jruby.gemfile +2 -0
- data/gemfiles/railsmaster.gemfile +2 -0
- data/lib/clowne.rb +3 -0
- data/lib/clowne/adapters/active_record.rb +2 -3
- data/lib/clowne/adapters/active_record/associations/base.rb +0 -4
- data/lib/clowne/adapters/active_record/associations/has_one.rb +2 -1
- data/lib/clowne/adapters/active_record/resolvers/association.rb +38 -0
- data/lib/clowne/adapters/base.rb +42 -43
- data/lib/clowne/adapters/base/association.rb +24 -15
- data/lib/clowne/adapters/registry.rb +49 -0
- data/lib/clowne/adapters/sequel.rb +10 -6
- data/lib/clowne/adapters/sequel/associations/base.rb +8 -4
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +6 -2
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +7 -2
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +7 -2
- data/lib/clowne/adapters/sequel/operation.rb +32 -0
- data/lib/clowne/adapters/sequel/record_wrapper.rb +0 -16
- data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +22 -0
- data/lib/clowne/adapters/sequel/resolvers/association.rb +51 -0
- data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +15 -0
- data/lib/clowne/cloner.rb +27 -20
- data/lib/clowne/declarations.rb +2 -1
- data/lib/clowne/declarations/after_persist.rb +21 -0
- data/lib/clowne/declarations/finalize.rb +1 -0
- data/lib/clowne/declarations/include_association.rb +2 -1
- data/lib/clowne/declarations/init_as.rb +1 -0
- data/lib/clowne/declarations/nullify.rb +1 -0
- data/lib/clowne/declarations/trait.rb +1 -0
- data/lib/clowne/dsl.rb +9 -0
- data/lib/clowne/ext/lambda_as_proc.rb +1 -0
- data/lib/clowne/ext/record_key.rb +12 -0
- data/lib/clowne/ext/yield_self_then.rb +25 -0
- data/lib/clowne/planner.rb +6 -3
- data/lib/clowne/resolvers/after_persist.rb +18 -0
- data/lib/clowne/resolvers/finalize.rb +12 -0
- data/lib/clowne/resolvers/init_as.rb +13 -0
- data/lib/clowne/resolvers/nullify.rb +15 -0
- data/lib/clowne/rspec/helpers.rb +1 -0
- data/lib/clowne/utils/clone_mapper.rb +26 -0
- data/lib/clowne/utils/operation.rb +83 -0
- data/lib/clowne/utils/options.rb +39 -0
- data/lib/clowne/utils/params.rb +64 -0
- data/lib/clowne/utils/plan.rb +90 -0
- data/lib/clowne/version.rb +1 -1
- metadata +44 -18
- data/docs/configuration.md +0 -29
- data/docs/execution_order.md +0 -14
- data/docs/web/static/fonts/StemText.woff +0 -0
- data/docs/web/static/fonts/StemTextBold.woff +0 -0
- data/lib/clowne/adapters/active_record/association.rb +0 -34
- data/lib/clowne/adapters/base/finalize.rb +0 -19
- data/lib/clowne/adapters/base/init_as.rb +0 -21
- data/lib/clowne/adapters/base/nullify.rb +0 -19
- data/lib/clowne/adapters/sequel/association.rb +0 -47
- data/lib/clowne/params.rb +0 -62
- data/lib/clowne/plan.rb +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1b3c79ac97f903ed3f2c1f8ae8b894dd7c4f3c4da5ab28350e59482ef08714e
|
4
|
+
data.tar.gz: af3aa849d2ba1dfdc34f8edbb612c384bafefd21d2ef74f2842e659af4690867
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbf985fe91770d1d34ae133492ba58aadc7eeab15b3dcc6f122db94d6e48e42dd8c885c4b145da2325583a98143d28464b128c7f34174a13f4922a72a340cfff
|
7
|
+
data.tar.gz: f47be4fac65591d29754756e2dd59a002d1eec0fcbfd661d2619f113020d97a7f1630d0d738cb3d2c9a7496d4302e2a0e888a3d537c9dcb2fed0053db72ee1db
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -5,7 +5,8 @@ notifications:
|
|
5
5
|
email: false
|
6
6
|
|
7
7
|
before_install:
|
8
|
-
gem update --system
|
8
|
+
- gem update --system
|
9
|
+
- gem install bundler
|
9
10
|
|
10
11
|
before_script:
|
11
12
|
# Only generate coverage report for the specified job
|
@@ -22,6 +23,8 @@ matrix:
|
|
22
23
|
include:
|
23
24
|
- rvm: ruby-head
|
24
25
|
gemfile: gemfiles/railsmaster.gemfile
|
26
|
+
- rvm: jruby-9.2.5.0
|
27
|
+
gemfile: gemfiles/jruby.gemfile
|
25
28
|
- rvm: jruby-9.1.15.0
|
26
29
|
gemfile: gemfiles/jruby.gemfile
|
27
30
|
- rvm: 2.5.0
|
@@ -35,11 +38,11 @@ matrix:
|
|
35
38
|
gemfile: gemfiles/activerecord42.gemfile
|
36
39
|
- rvm: 2.3.1
|
37
40
|
gemfile: Gemfile
|
38
|
-
- rvm: 2.2.0
|
39
|
-
gemfile: gemfiles/activerecord42.gemfile
|
40
41
|
allow_failures:
|
41
42
|
- rvm: ruby-head
|
42
43
|
gemfile: gemfiles/railsmaster.gemfile
|
44
|
+
- rvm: jruby-9.2.5.0
|
45
|
+
gemfile: gemfiles/jruby.gemfile
|
43
46
|
- rvm: jruby-9.1.15.0
|
44
47
|
gemfile: gemfiles/jruby.gemfile
|
45
48
|
deploy:
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## 1.0.0 (2019-02-26)
|
4
|
+
|
5
|
+
- Return `Operation` instance as a rusult of cloning. ([@ssnickolay][])
|
6
|
+
|
7
|
+
See [migration guide](https://clowne.evilmartians.io/docs/from_v02_to_v10.html)
|
8
|
+
|
9
|
+
- Add `after_persist` declaration. ([@ssnickolay][], [@palkan][])
|
10
|
+
|
11
|
+
- Unify interface between adapters. ([@ssnickolay][])
|
12
|
+
|
13
|
+
- Deprecate `Operation#save` and `Operation#save!` methods. ([@ssnickolay][])
|
14
|
+
|
15
|
+
- Improve Docs ([@ssnickolay][], [@palkan][])
|
16
|
+
|
3
17
|
## 0.2.0 (2018-02-21)
|
4
18
|
|
5
19
|
- Add `Cloner#partial_apply` method. ([@palkan][])
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/clowne.svg)](https://badge.fury.io/rb/clowne)
|
2
2
|
[![Build Status](https://travis-ci.org/palkan/clowne.svg?branch=master)](https://travis-ci.org/palkan/clowne)
|
3
3
|
[![Test Coverage](https://codeclimate.com/github/palkan/clowne/badges/coverage.svg)](https://codeclimate.com/github/palkan/clowne/coverage)
|
4
|
-
[![Docs](https://img.shields.io/
|
4
|
+
[![Docs](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://clowne.evilmartians.io)
|
5
5
|
|
6
6
|
# Clowne
|
7
7
|
|
8
8
|
A flexible gem for cloning your models. Clowne focuses on ease of use and provides the ability to connect various ORM adapters.
|
9
9
|
|
10
|
+
📖 Read [Evil Martians Chronicles](https://evilmartians.com/chronicles/clowne-clone-ruby-models-with-a-smile) to learn about possible use cases.
|
11
|
+
|
12
|
+
📑 [Documentation](https://clowne.evilmartians.io)
|
13
|
+
|
10
14
|
<a href="https://evilmartians.com/">
|
11
15
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
12
16
|
|
@@ -40,6 +44,16 @@ class User < ActiveRecord::Base
|
|
40
44
|
has_one :profile
|
41
45
|
has_many :posts
|
42
46
|
end
|
47
|
+
|
48
|
+
class Profile < ActiveRecord::Base
|
49
|
+
# create_table :profiles do |t|
|
50
|
+
# t.string :name
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Post < ActiveRecord::Base
|
55
|
+
# create_table :posts
|
56
|
+
end
|
43
57
|
```
|
44
58
|
|
45
59
|
Let's declare our cloners first:
|
@@ -70,13 +84,20 @@ Now you can use `UserCloner` to clone existing records:
|
|
70
84
|
|
71
85
|
```ruby
|
72
86
|
user = User.last
|
73
|
-
|
87
|
+
# => <#User id: 1, login: 'clown', email: 'clown@circus.example.com'>
|
88
|
+
|
89
|
+
operation = UserCloner.call(user, email: 'fake@example.com')
|
90
|
+
# => <#Clowne::Utils::Operation...>
|
91
|
+
|
92
|
+
operation.to_record
|
93
|
+
# => <#User id: nil, login: nil, email: 'fake@example.com'>
|
94
|
+
|
95
|
+
operation.persist!
|
96
|
+
# => true
|
74
97
|
|
75
|
-
cloned =
|
76
|
-
|
77
|
-
# => false
|
98
|
+
cloned = operation.to_record
|
99
|
+
# => <#User id: 2, login: nil, email: 'fake@example.com'>
|
78
100
|
|
79
|
-
cloned.save!
|
80
101
|
cloned.login
|
81
102
|
# => nil
|
82
103
|
cloned.email
|
@@ -89,14 +110,14 @@ cloned.profile.name
|
|
89
110
|
# => nil
|
90
111
|
```
|
91
112
|
|
92
|
-
Take a look at our [documentation](https://
|
113
|
+
Take a look at our [documentation](https://clowne.evilmartians.io) for more info!
|
93
114
|
|
94
115
|
### Supported ORM adapters
|
95
116
|
|
96
117
|
Adapter |1:1 | 1:M | M:M |
|
97
118
|
------------------------------------------|------------|-------------|-------------------------|
|
98
|
-
[Active Record](https://
|
99
|
-
[Sequel](https://
|
119
|
+
[Active Record](https://clowne.evilmartians.io/clowne/docs/active_record.html) | has_one | has_many | has_and_belongs_to|
|
120
|
+
[Sequel](https://clowne.evilmartians.io/clowne/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
|
100
121
|
|
101
122
|
## Maintainers
|
102
123
|
|
data/clowne.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
lib = File.expand_path('
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
3
|
require 'clowne/version'
|
4
4
|
|
@@ -20,10 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
|
-
spec.add_development_dependency 'bundler', '~>
|
23
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
24
24
|
spec.add_development_dependency 'rake', '~> 10.0'
|
25
25
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
26
|
spec.add_development_dependency 'factory_bot', '~> 4.8'
|
27
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
27
|
+
spec.add_development_dependency 'rubocop', '~> 0.61'
|
28
28
|
spec.add_development_dependency 'rubocop-md', '~> 0.2'
|
29
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.31'
|
29
30
|
end
|
data/docs/active_record.md
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
---
|
2
|
+
id: after_persist
|
3
|
+
title: After Persist
|
4
|
+
---
|
5
|
+
|
6
|
+
_Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter._
|
7
|
+
|
8
|
+
The special mechanism for transformation of cloned record. In contradistinction to [`finalize`](finalize.md) executes with a saved record. This type of callbacks provides a default `mapper:` parameter which contains a relation between origin and cloned objects.
|
9
|
+
|
10
|
+
`after_persist` helps to restore broken _relationships_ while cloning associations and implement some logic with already persisted clone record. (_Inspired by [issues#19](https://github.com/palkan/clowne/issues/19)_)
|
11
|
+
|
12
|
+
Examples:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class User < ActiveRecord::Base
|
16
|
+
# create_table :users do |t|
|
17
|
+
# t.string :login
|
18
|
+
# t.integer :bio_id
|
19
|
+
# end
|
20
|
+
|
21
|
+
has_many :posts # all user's posts including BIO
|
22
|
+
belongs_to :bio, class_name: 'Post'
|
23
|
+
end
|
24
|
+
|
25
|
+
class Post < ActiveRecord::Base
|
26
|
+
# create_table :posts do |t|
|
27
|
+
# t.integer :user_id
|
28
|
+
# end
|
29
|
+
end
|
30
|
+
|
31
|
+
class UserCloner < Clowne::Cloner
|
32
|
+
include_association :posts, params: true
|
33
|
+
|
34
|
+
after_persist do |origin, clone, mapper:, **|
|
35
|
+
cloned_bio = mapper.clone_of(origin.bio)
|
36
|
+
clone.update_attributes(bio_id: cloned_bio.id)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class PostCloner < Clowne::Cloner
|
41
|
+
after_persist do |_, clone, run_job:, **|
|
42
|
+
PostBackgroundJob.perform_async(clone.id) if run_job
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
_Notice: See more about `mapper:` [`here`](clone_mapper.md)._
|
48
|
+
|
49
|
+
`after_persist` runs when you call [`Operation#persist`]('operation.md) (or `Operation#persist!`)
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# prepare data
|
53
|
+
user = User.create
|
54
|
+
posts = Array.new(3) { Post.create(user: user) }
|
55
|
+
bio = posts.sample
|
56
|
+
user.update_attributes(bio_id: bio.id)
|
57
|
+
|
58
|
+
operation = UserCloner.call(user, run_job: true)
|
59
|
+
# => <#Clowne::Utils::Operation ...>
|
60
|
+
|
61
|
+
clone = operation.to_record
|
62
|
+
# => <#User id: nil, ...>
|
63
|
+
|
64
|
+
# we copy all user attributes including bio_id
|
65
|
+
# but this is wrong because bio refers to the source user's bio
|
66
|
+
# and we can fix it using after_persist when posts already saved
|
67
|
+
clone.bio_id == bio.id
|
68
|
+
# => true
|
69
|
+
|
70
|
+
# save clone and run after_persist callbacks
|
71
|
+
operation.persit
|
72
|
+
|
73
|
+
clone.bio_id == bio.id
|
74
|
+
# => false
|
75
|
+
|
76
|
+
clone.posts.pluck(:id).include?(clone.bio_id)
|
77
|
+
# => true
|
78
|
+
```
|
79
|
+
|
80
|
+
_Notice: be careful while using after_persist feature! If you clone a fat record (with a lot of associations) and will implement complex logic inside `after_persist` callback, it may affect your system._
|
data/docs/basic_example.md
CHANGED
@@ -16,6 +16,16 @@ class User < ActiveRecord::Base
|
|
16
16
|
has_one :profile
|
17
17
|
has_many :posts
|
18
18
|
end
|
19
|
+
|
20
|
+
class Profile < ActiveRecord::Base
|
21
|
+
# create_table :profiles do |t|
|
22
|
+
# t.string :name
|
23
|
+
# end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Post < ActiveRecord::Base
|
27
|
+
# create_table :posts
|
28
|
+
end
|
19
29
|
```
|
20
30
|
|
21
31
|
Let's declare our cloners first:
|
@@ -46,13 +56,20 @@ Now you can use `UserCloner` to clone existing records:
|
|
46
56
|
|
47
57
|
```ruby
|
48
58
|
user = User.last
|
49
|
-
|
59
|
+
# => <#User id: 1, login: 'clown', email: 'clown@circus.example.com'>
|
60
|
+
|
61
|
+
operation = UserCloner.call(user, email: 'fake@example.com')
|
62
|
+
# => <#Clowne::Utils::Operation...>
|
63
|
+
|
64
|
+
operation.to_record
|
65
|
+
# => <#User id: nil, login: nil, email: 'fake@example.com'>
|
66
|
+
|
67
|
+
operation.persist!
|
68
|
+
# => true
|
50
69
|
|
51
|
-
cloned =
|
52
|
-
|
53
|
-
# => false
|
70
|
+
cloned = operation.to_record
|
71
|
+
# => <#User id: 2, login: nil, email: 'fake@example.com'>
|
54
72
|
|
55
|
-
cloned.save!
|
56
73
|
cloned.login
|
57
74
|
# => nil
|
58
75
|
cloned.email
|
@@ -0,0 +1,62 @@
|
|
1
|
+
---
|
2
|
+
id: clone_mapper
|
3
|
+
title: Clone mapper
|
4
|
+
---
|
5
|
+
|
6
|
+
_Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter._
|
7
|
+
|
8
|
+
In [`after_persist`](after_persist.md) documenation you can find interisting code:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class UserCloner < Clowne::Cloner
|
12
|
+
# ...
|
13
|
+
after_persist do |origin, clone, mapper:, **|
|
14
|
+
cloned_bio = mapper.clone_of(origin.bio)
|
15
|
+
clone.update_attributes(bio_id: cloned_bio.id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
What is it `mapper:` and how it works?
|
21
|
+
|
22
|
+
`mapper:` is an instance of `Clowne::Utils::CloneMapper`. Active Record pattern gives us an opportunity to build our cloning tool on objects, so while cloning, we remember origin records and their clones and can use these relations after saving with `Clowne::Utils::CloneMapper`.
|
23
|
+
|
24
|
+
It perfectly works, and we can use the mapper to fix broken associations.
|
25
|
+
There is only one small nuance - we can get `#clone_of` only for the record that participated in a cloning process, and this limits the functionality of the `after_persist` callbacks.
|
26
|
+
|
27
|
+
But `Clowne is built with extensibility in mind™` and Clowne provides the ability to use your custom mapper:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class CustomMapper < Clowne::Utils::CloneMapper
|
31
|
+
def initialize(dependencies)
|
32
|
+
super()
|
33
|
+
# inject dependencies if need
|
34
|
+
# or fetch it from other place (e.g. from DB)
|
35
|
+
@default_bios = dependencies.index_by(&:title)
|
36
|
+
end
|
37
|
+
|
38
|
+
def clone_of(record)
|
39
|
+
super(record) || fallback(record)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def fallback(record)
|
45
|
+
# put some mapping logic here
|
46
|
+
return if record.is_a?(Post)
|
47
|
+
|
48
|
+
@default_bios[record.title]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# now we can use it:
|
53
|
+
|
54
|
+
class Post < ActiveRecord::Base
|
55
|
+
# add simple scope
|
56
|
+
# scope :default_bios, -> {...}
|
57
|
+
end
|
58
|
+
|
59
|
+
user = User.last
|
60
|
+
operation = UserCloner.call(user, mapper: CustomMapper.new(Post.default_bios))
|
61
|
+
operation.persist
|
62
|
+
```
|
data/docs/customization.md
CHANGED
@@ -21,7 +21,7 @@ class IncludeAll < Clowne::Declarations::Base # :nodoc: all
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Register our declrations, i.e. extend DSL
|
24
|
-
Clowne::Declarations.add :include_all,
|
24
|
+
Clowne::Declarations.add :include_all, IncludeAll
|
25
25
|
```
|
26
26
|
|
27
27
|
See more on `plan` in [architecture overview](architecture.md).
|
@@ -40,6 +40,7 @@ class AllAssociations
|
|
40
40
|
source.class.reflections.each_value do |_name, reflection|
|
41
41
|
# Exclude belongs_to associations
|
42
42
|
next if reflection.macro == :belongs_to
|
43
|
+
|
43
44
|
# Resolve and apply association cloner
|
44
45
|
cloner_class = Clowne::Adapters::ActiveRecord::Associations.cloner_for(reflection)
|
45
46
|
cloner_class.new(reflection, source, declaration, params).call(record)
|
data/docs/exclude_association.md
CHANGED
@@ -19,12 +19,12 @@ class UserCloner < Clowne::Cloner
|
|
19
19
|
end
|
20
20
|
|
21
21
|
# copy user and posts
|
22
|
-
clone = UserCloner.call(user)
|
22
|
+
clone = UserCloner.call(user).to_record
|
23
23
|
clone.posts.count == user.posts.count
|
24
24
|
# => true
|
25
25
|
|
26
26
|
# copy only user
|
27
|
-
clone2 = UserCloner.call(user, traits: :without_posts)
|
27
|
+
clone2 = UserCloner.call(user, traits: :without_posts).to_record
|
28
28
|
clone2.posts
|
29
29
|
# => []
|
30
30
|
```
|
@@ -41,8 +41,9 @@ class UserCloner < Clowne::Cloner
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
clone = UserCloner.call(user, traits: :with_comments)
|
45
|
-
clone.comments.empty?
|
44
|
+
clone = UserCloner.call(user, traits: :with_comments).to_record
|
45
|
+
clone.comments.empty?
|
46
|
+
# => true
|
46
47
|
```
|
47
48
|
|
48
49
|
Why so? That allows us to have a deterministic cloning plan when combining multiple traits
|
data/docs/finalize.md
CHANGED
@@ -19,13 +19,13 @@ class UserCloner < Clowne::Cloner
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
cloned = UserCloner.call(user)
|
22
|
+
cloned = UserCloner.call(user).to_record
|
23
23
|
cloned.name
|
24
24
|
# => 'This is copy!'
|
25
25
|
cloned.email == 'clone@example.com'
|
26
26
|
# => false
|
27
27
|
|
28
|
-
cloned2 = UserCloner.call(user, traits: :change_email)
|
28
|
+
cloned2 = UserCloner.call(user, traits: :change_email).to_record
|
29
29
|
cloned2.name
|
30
30
|
# => 'This is copy!'
|
31
31
|
cloned2.email
|
@@ -0,0 +1,91 @@
|
|
1
|
+
---
|
2
|
+
id: from_v02_to_v10
|
3
|
+
title: From v0.2.x to v1.0.0
|
4
|
+
---
|
5
|
+
|
6
|
+
The breaking change of v1.0 is the return of a unified [`result object`](operation.md) for all adapters.
|
7
|
+
|
8
|
+
## ActiveRecord
|
9
|
+
|
10
|
+
### Update code to work with [`Operation`](operation.md)
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
# Before
|
14
|
+
clone = UserCloner.call(user)
|
15
|
+
# => <#User id: nil, ...>
|
16
|
+
clone.save!
|
17
|
+
# => true
|
18
|
+
|
19
|
+
# After
|
20
|
+
clone = UserCloner.call(user)
|
21
|
+
# => <#Clowne::Utils::Operation ...>
|
22
|
+
clone = clone.to_record
|
23
|
+
# => <#User id: 2, ...>
|
24
|
+
clone.save!
|
25
|
+
# => true
|
26
|
+
|
27
|
+
# After (even better because of using full functionality)
|
28
|
+
operation = UserCloner.call(user)
|
29
|
+
# => <#Clowne::Utils::Operation ...>
|
30
|
+
operation.persist!
|
31
|
+
# => true
|
32
|
+
clone = operation.to_record
|
33
|
+
# => <#User id: 2, ...>
|
34
|
+
clone.persisted?
|
35
|
+
# => true
|
36
|
+
```
|
37
|
+
|
38
|
+
### Move post-processing cloning logic into [`after_persist`](after_persist.md) callback (if you have it)
|
39
|
+
|
40
|
+
_Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter._
|
41
|
+
|
42
|
+
<span style="display:none;"># rubocop:disable all</span>
|
43
|
+
```ruby
|
44
|
+
# Before
|
45
|
+
clone = UserCloner.call(user)
|
46
|
+
clone.save!
|
47
|
+
# do something with persisted clone
|
48
|
+
|
49
|
+
# After
|
50
|
+
class UserCloner < Clowne::Cloner
|
51
|
+
# ...
|
52
|
+
after_persist do |origin, clone, **|
|
53
|
+
# do something with persisted clone
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
clone = UserCloner.call(user).tap(&:persist).to_record
|
58
|
+
```
|
59
|
+
<span style="display:none;"># rubocop:enable all</span>
|
60
|
+
|
61
|
+
## Sequel
|
62
|
+
|
63
|
+
### Use `to_record` instead of `to_model`
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# Before
|
67
|
+
record_wrapper = UserCloner.call(user)
|
68
|
+
clone = record_wrapper.to_model
|
69
|
+
clone.new?
|
70
|
+
# => true
|
71
|
+
|
72
|
+
# After
|
73
|
+
operation = UserCloner.call(user)
|
74
|
+
clone = operation.to_record
|
75
|
+
clone.new?
|
76
|
+
# => true
|
77
|
+
```
|
78
|
+
|
79
|
+
### Use `operation#persist` instead of converting to model and calling `#save`
|
80
|
+
|
81
|
+
<span style="display:none;"># rubocop:disable all</span>
|
82
|
+
```ruby
|
83
|
+
# Before
|
84
|
+
record_wrapper = UserCloner.call(user)
|
85
|
+
clone = record_wrapper.to_model
|
86
|
+
clone.save
|
87
|
+
|
88
|
+
# After
|
89
|
+
clone = UserCloner.call(user).tap(&:persist).to_record
|
90
|
+
```
|
91
|
+
<span style="display:none;"># rubocop:enable all</span>
|