clowne 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.travis.yml +6 -3
  4. data/CHANGELOG.md +14 -0
  5. data/Gemfile +1 -1
  6. data/README.md +30 -9
  7. data/clowne.gemspec +4 -3
  8. data/docs/active_record.md +2 -2
  9. data/docs/after_persist.md +80 -0
  10. data/docs/basic_example.md +22 -5
  11. data/docs/clone_mapper.md +62 -0
  12. data/docs/customization.md +2 -1
  13. data/docs/exclude_association.md +5 -4
  14. data/docs/finalize.md +2 -2
  15. data/docs/from_v02_to_v1.md +91 -0
  16. data/docs/implicit_cloner.md +1 -1
  17. data/docs/include_association.md +4 -4
  18. data/docs/init_as.md +10 -2
  19. data/docs/inline_configuration.md +4 -2
  20. data/docs/installation.md +31 -1
  21. data/docs/nullify.md +2 -2
  22. data/docs/operation.md +58 -0
  23. data/docs/overview.md +24 -0
  24. data/docs/parameters.md +5 -4
  25. data/docs/sequel.md +15 -18
  26. data/docs/supported_adapters.md +2 -2
  27. data/docs/testing.md +7 -5
  28. data/docs/web/README.md +6 -0
  29. data/docs/web/core/Footer.js +5 -9
  30. data/docs/web/i18n/en.json +8 -4
  31. data/docs/web/pages/en/index.js +1 -1
  32. data/docs/web/sidebars.json +10 -4
  33. data/docs/web/siteConfig.js +6 -4
  34. data/docs/web/static/css/custom.css +16 -10
  35. data/gemfiles/activerecord42.gemfile +3 -1
  36. data/gemfiles/jruby.gemfile +2 -0
  37. data/gemfiles/railsmaster.gemfile +2 -0
  38. data/lib/clowne.rb +3 -0
  39. data/lib/clowne/adapters/active_record.rb +2 -3
  40. data/lib/clowne/adapters/active_record/associations/base.rb +0 -4
  41. data/lib/clowne/adapters/active_record/associations/has_one.rb +2 -1
  42. data/lib/clowne/adapters/active_record/resolvers/association.rb +38 -0
  43. data/lib/clowne/adapters/base.rb +42 -43
  44. data/lib/clowne/adapters/base/association.rb +24 -15
  45. data/lib/clowne/adapters/registry.rb +49 -0
  46. data/lib/clowne/adapters/sequel.rb +10 -6
  47. data/lib/clowne/adapters/sequel/associations/base.rb +8 -4
  48. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +6 -2
  49. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +7 -2
  50. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +7 -2
  51. data/lib/clowne/adapters/sequel/operation.rb +32 -0
  52. data/lib/clowne/adapters/sequel/record_wrapper.rb +0 -16
  53. data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +22 -0
  54. data/lib/clowne/adapters/sequel/resolvers/association.rb +51 -0
  55. data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +15 -0
  56. data/lib/clowne/cloner.rb +27 -20
  57. data/lib/clowne/declarations.rb +2 -1
  58. data/lib/clowne/declarations/after_persist.rb +21 -0
  59. data/lib/clowne/declarations/finalize.rb +1 -0
  60. data/lib/clowne/declarations/include_association.rb +2 -1
  61. data/lib/clowne/declarations/init_as.rb +1 -0
  62. data/lib/clowne/declarations/nullify.rb +1 -0
  63. data/lib/clowne/declarations/trait.rb +1 -0
  64. data/lib/clowne/dsl.rb +9 -0
  65. data/lib/clowne/ext/lambda_as_proc.rb +1 -0
  66. data/lib/clowne/ext/record_key.rb +12 -0
  67. data/lib/clowne/ext/yield_self_then.rb +25 -0
  68. data/lib/clowne/planner.rb +6 -3
  69. data/lib/clowne/resolvers/after_persist.rb +18 -0
  70. data/lib/clowne/resolvers/finalize.rb +12 -0
  71. data/lib/clowne/resolvers/init_as.rb +13 -0
  72. data/lib/clowne/resolvers/nullify.rb +15 -0
  73. data/lib/clowne/rspec/helpers.rb +1 -0
  74. data/lib/clowne/utils/clone_mapper.rb +26 -0
  75. data/lib/clowne/utils/operation.rb +83 -0
  76. data/lib/clowne/utils/options.rb +39 -0
  77. data/lib/clowne/utils/params.rb +64 -0
  78. data/lib/clowne/utils/plan.rb +90 -0
  79. data/lib/clowne/version.rb +1 -1
  80. metadata +44 -18
  81. data/docs/configuration.md +0 -29
  82. data/docs/execution_order.md +0 -14
  83. data/docs/web/static/fonts/StemText.woff +0 -0
  84. data/docs/web/static/fonts/StemTextBold.woff +0 -0
  85. data/lib/clowne/adapters/active_record/association.rb +0 -34
  86. data/lib/clowne/adapters/base/finalize.rb +0 -19
  87. data/lib/clowne/adapters/base/init_as.rb +0 -21
  88. data/lib/clowne/adapters/base/nullify.rb +0 -19
  89. data/lib/clowne/adapters/sequel/association.rb +0 -47
  90. data/lib/clowne/params.rb +0 -62
  91. data/lib/clowne/plan.rb +0 -83
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 616d875656dc9fa7ef815190a4281d5cac5ebd8d6ac471bd6b7292a139f78d8e
4
- data.tar.gz: 35fd2f7681c3e13152ad98562737e6bac498c79e569f330a9986eba2e45302a2
3
+ metadata.gz: f1b3c79ac97f903ed3f2c1f8ae8b894dd7c4f3c4da5ab28350e59482ef08714e
4
+ data.tar.gz: af3aa849d2ba1dfdc34f8edbb612c384bafefd21d2ef74f2842e659af4690867
5
5
  SHA512:
6
- metadata.gz: 81734963c899011b5f518d817dddeb4fe761df3326ec2165b99b877212ec8de19100d9cb2cd5beccebab276c42f0c6e806d6385ce2f929982c3509641e6c58b9
7
- data.tar.gz: e6fa4181b54efcd3a33e9be9dc5a9f569f324954d9d72d4cb95a253bc8c5b1386e30060c52f9f6434ee3c2f03dd2e9f86c0e21f254fab9725efd927c2d07dda9
6
+ metadata.gz: bbf985fe91770d1d34ae133492ba58aadc7eeab15b3dcc6f122db94d6e48e42dd8c885c4b145da2325583a98143d28464b128c7f34174a13f4922a72a340cfff
7
+ data.tar.gz: f47be4fac65591d29754756e2dd59a002d1eec0fcbfd661d2619f113020d97a7f1630d0d738cb3d2c9a7496d4302e2a0e888a3d537c9dcb2fed0053db72ee1db
@@ -24,6 +24,12 @@ Naming/ClassAndModuleCamelCase:
24
24
  Exclude:
25
25
  - 'spec/**/*.rb'
26
26
 
27
+ Naming/UncommunicativeMethodParamName:
28
+ Enabled: false
29
+
30
+ Naming/MemoizedInstanceVariableName:
31
+ Enabled: false
32
+
27
33
  Style/TrivialAccessors:
28
34
  Enabled: false
29
35
 
@@ -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:
@@ -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
@@ -5,7 +5,7 @@ gemspec
5
5
 
6
6
  gem 'pry-byebug', platform: :mri
7
7
 
8
- gem 'sqlite3', platform: :mri
8
+ gem 'sqlite3', '~> 1.3.6', platform: :mri
9
9
  gem 'activerecord-jdbcsqlite3-adapter', '~> 50.0', platform: :jruby
10
10
  gem 'jdbc-sqlite3', platform: :jruby
11
11
 
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/readthedocs/pip.svg)](https://palkan.github.io/clowne)
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
- #=> <#User(login: 'clown', email: 'clown@circus.example.com')>
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 = UserCloner.call(user, email: 'fake@example.com')
76
- cloned.persisted?
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://palkan.github.io/clowne) for more information!
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://palkan.github.io/clowne/docs/active_record.html) | has_one | has_many | has_and_belongs_to_many |
99
- [Sequel](https://palkan.github.io/clowne/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
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
 
@@ -1,4 +1,4 @@
1
- lib = File.expand_path('../lib', __FILE__)
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', '~> 1.14'
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.51'
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
@@ -31,6 +31,6 @@ end
31
31
  And then you can clone objects like this:
32
32
 
33
33
  ```ruby
34
- user.clowne(traits: my_traits, **params)
35
- # => <#User...
34
+ user.clowne(traits: my_traits, **params).to_record
35
+ # => <#User id: nil...>
36
36
  ```
@@ -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._
@@ -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
- #=> <#User(login: 'clown', email: 'clown@circus.example.com')>
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 = UserCloner.call(user, email: 'fake@example.com')
52
- cloned.persisted?
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
+ ```
@@ -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, Clowne::Declarations::IncludeAll
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)
@@ -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? #=> true
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
@@ -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>