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
@@ -28,7 +28,7 @@ user = User.last
28
28
  user.profile.name
29
29
  #=> "Bimbo"
30
30
 
31
- cloned = UserCloner.call(user)
31
+ cloned = UserCloner.call(user).to_record
32
32
  cloned.profile.name
33
33
  # => "Clone of Bimbo"
34
34
  ```
@@ -51,8 +51,8 @@ class UserCloner < Clowne::Cloner
51
51
  end
52
52
 
53
53
  # Clone only draft posts
54
- UserCloner.call(user, state: :draft)
55
- # => <#User...
54
+ UserCloner.call(user, state: :draft).to_record
55
+ # => <#User id: nil, ... >
56
56
  ```
57
57
 
58
58
  ## Options
@@ -93,8 +93,8 @@ class UserCloner < Clowne::Cloner
93
93
  # include_association :posts, clone_with: PostSpecialCloner, traits: :with_tags
94
94
  end
95
95
 
96
- UserCloner.call(user)
97
- # => <#User...
96
+ UserCloner.call(user).to_record
97
+ # => <#User id: nil, ... >
98
98
  ```
99
99
 
100
100
  **NOTE**: if custom cloner is not defined, Clowne tries to infer the [implicit cloner](implicit_cloner.md).
@@ -28,9 +28,17 @@ class UserCloner < Clowne::Cloner
28
28
  end
29
29
 
30
30
  jack = User.find_by(email: 'jack@evl.ms')
31
+ # => <#User id: 1, ...>
32
+ jack.create_profile(name: 'Jack')
33
+ # => <#Profile id: 1, name: 'Jack', ...>
34
+
31
35
  john = User.find_by(email: 'john@evl.ms')
36
+ # => <#User id: 2, ...>
32
37
 
33
- # we want to clone Jack's profile settings to another user,
38
+ # we want to clone Jack's profile to John's user,
34
39
  # without creating a new one
35
- UserCloner.call(jack, traits: :copy_settings, target: john)
40
+ john_with_profile = UserCloner.call(jack, traits: :copy_settings, target: john).to_record
41
+ # => <#User id: 2, ...>
42
+ john_with_profile.profile
43
+ #=> <#Profile id: nil, name: 'Jack',...>
36
44
  ```
@@ -6,7 +6,7 @@ title: Inline Configuration
6
6
  You can also enhance the cloner configuration inline (i.e., add declarations dynamically):
7
7
 
8
8
  ```ruby
9
- cloned = UserCloner.call(User.last) do
9
+ operation = UserCloner.call(User.last) do
10
10
  exclude_association :profile
11
11
 
12
12
  finalize do |source, record|
@@ -14,6 +14,8 @@ cloned = UserCloner.call(User.last) do
14
14
  end
15
15
  end
16
16
 
17
+ cloned = operation.to_record
18
+
17
19
  cloned.email
18
20
  # => "clone_of_john@example.com"
19
21
 
@@ -31,7 +33,7 @@ Thus it's also possible to clone objects without any cloner classes at all by us
31
33
  ```ruby
32
34
  cloned = Clowne::Cloner.call(user) do
33
35
  # anything you want!
34
- end
36
+ end.to_record
35
37
 
36
38
  cloned
37
39
  # => <#User..
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  id: installation
3
- title: Installation
3
+ title: Installation & Configuration
4
4
  ---
5
5
 
6
+ ## Installation
7
+
6
8
  To install Clowne with RubyGems:
7
9
 
8
10
  ```ruby
@@ -14,3 +16,31 @@ Or add this line to your application's Gemfile:
14
16
  ```ruby
15
17
  gem 'clowne'
16
18
  ```
19
+
20
+ ## Configuration
21
+
22
+ Basic cloner implementation looks like:
23
+
24
+ ```ruby
25
+ class SomeCloner < Clowne::Cloner
26
+ adapter :active_record # or adapter Clowne::Adapters::ActiveRecord
27
+ # some implementation ...
28
+ end
29
+ ```
30
+
31
+ You can configure the default adapter for cloners:
32
+
33
+ ```ruby
34
+ # put to initializer
35
+ # e.g. config/initializers/clowne.rb
36
+ Clowne.default_adapter = :active_record
37
+ ```
38
+
39
+ and skip explicit adapter declaration
40
+
41
+ ```ruby
42
+ class SomeCloner < Clowne::Cloner
43
+ # some implementation ...
44
+ end
45
+ ```
46
+ See the list of [available adapters](supported_adapters.md).
@@ -21,7 +21,7 @@ class UserCloner < Clowne::Cloner
21
21
  end
22
22
  end
23
23
 
24
- clone = UserCloner.call(user)
24
+ clone = UserCloner.call(user).to_record
25
25
  clone.name.nil?
26
26
  # => true
27
27
  clone.email.nil?
@@ -29,7 +29,7 @@ clone.email.nil?
29
29
  clone.surname.nil?
30
30
  # => false
31
31
 
32
- clone2 = UserCloner.call(user, traits: :nullify_surname)
32
+ clone2 = UserCloner.call(user, traits: :nullify_surname).to_record
33
33
  clone2.name.nil?
34
34
  # => true
35
35
  clone2.surname.nil?
@@ -0,0 +1,58 @@
1
+ ---
2
+ id: operation
3
+ title: Operation
4
+ ---
5
+
6
+ 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
+
8
+ An instance of `Operation` has a very clear interface:
9
+
10
+ ```ruby
11
+ class User < ActiveRecord::Base; end
12
+
13
+ class UserCloner < Clowne::Cloner
14
+ nullify :email
15
+
16
+ after_persist do |_origin, cloned, **|
17
+ cloned.update_attributes(email: "evl-#{cloned.id}.ms")
18
+ end
19
+ end
20
+
21
+ user = User.create(email: 'evl.ms')
22
+ # => <#User id: 1, email: 'evl.ms', ...>
23
+
24
+ operation = UserCloner.call(user)
25
+
26
+ # Return resulted (non saved) object:
27
+ operation.to_record
28
+ # => <#User id: nil, email: nil, ...>
29
+
30
+ # Save cloned object and call after_persist callbacks:
31
+ operation.persist # or operation.persist!
32
+ # => true
33
+
34
+ operation.to_record
35
+ # => <#User id: 2, email: 'evl-2.ms', ...>
36
+
37
+ # Call only after_persist callbacks:
38
+ user2 = operation.to_record
39
+ # => <#User id: 2, email: 'evl-2.ms', ...>
40
+ user2.update_attributes(email: 'admin@example.com')
41
+ # => <#User id: 2, email: 'admin@example.com' ...>
42
+ operation.run_after_persist
43
+ # => <#User id: 2, email: 'evl-2.ms', ...>
44
+ ```
45
+
46
+ The last example is weird, but it can be helpful when you need to execute `save` (or `save!`) separately from `after_persist` callbacks:
47
+
48
+ ```ruby
49
+ operation = UserClone.call(user)
50
+
51
+ # Wrap main cloning into the transaction
52
+ ActiveRecord::Base.transaction do
53
+ operation.to_record.save!
54
+ end
55
+
56
+ # And after that execute after_persist without transaction
57
+ operation.run_after_persist
58
+ ```
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: overview
3
+ title: Overview
4
+ ---
5
+
6
+ In [the basic example](basic_example.md), you can see that Clowne consists of flexible DSL which is used in a class inherited of `Clowne::Cloner`.
7
+
8
+ You can combinate this DSL via [`traits`](traits.md) and make a cloning plan which exactly you want.
9
+
10
+ **We strongly recommend [`write tests`](testing.md) to cover resulting cloner logic**
11
+
12
+ 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.
13
+
14
+ ## Execution Order
15
+
16
+ The order of cloning actions depends on the adapter (i.e., could be customized).
17
+
18
+ All built-in adapters have the same order and what happens when you call `Operation#persist`:
19
+ - init clone (see [`init_as`](init_as.md)) (empty by default)
20
+ - [`clone associations`](include_association.md)
21
+ - [`nullify`](nullify.md) attributes
22
+ - run [`finalize`](finalize.md) blocks. _The order of [`finalize`](finalize.md) blocks is the order they've been written._
23
+ - __SAVE CLONED RECORD__
24
+ - run [`after_persist`](after_persist.md) callbacks
@@ -16,7 +16,8 @@ class UserCloner < Clowne::Cloner
16
16
  end
17
17
  end
18
18
 
19
- cloned = UserCloner.call(user, state: :draft, email: 'cloned@example.com')
19
+ operation = UserCloner.call(user, state: :draft, email: 'cloned@example.com')
20
+ cloned = operation.to_record
20
21
  cloned.email
21
22
  # => 'cloned@example.com'
22
23
  ```
@@ -54,7 +55,7 @@ class UserCloner < Clowne::Cloner
54
55
  end
55
56
 
56
57
  # Pass all parameters to associations
57
- trait :params_true do
58
+ trait :all_params do
58
59
  include_association :profile, params: true
59
60
  end
60
61
 
@@ -93,14 +94,14 @@ end
93
94
 
94
95
  def get_profile_jsonb(user, trait)
95
96
  params = { profile: { name: 'John', surname: 'Cena' } }
96
- cloned = UserCloner.call(user, traits: trait, **params)
97
+ cloned = UserCloner.call(user, traits: trait, **params).to_record
97
98
  cloned.profile.jsonb_field
98
99
  end
99
100
 
100
101
  get_profile_jsonb(user, :default)
101
102
  # => {}
102
103
 
103
- get_profile_jsonb(user, :params_true)
104
+ get_profile_jsonb(user, :all_params)
104
105
  # => { profile: { name: 'John', surname: 'Cena' } }
105
106
 
106
107
  get_profile_jsonb(user, :by_key)
@@ -3,9 +3,7 @@ id: sequel
3
3
  title: Sequel
4
4
  ---
5
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).
6
+ 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.
9
7
 
10
8
  Example:
11
9
 
@@ -29,13 +27,11 @@ and get cloned user
29
27
 
30
28
  ```ruby
31
29
  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?
30
+ operation = UserCloner.call(user)
31
+ # => <#Clowne::Adapters::Sequel::Operation...>
32
+ cloned = operation.to_record
33
+ # => <#User id: nil, ...>
34
+ cloned.new?
39
35
  # => true
40
36
  ```
41
37
 
@@ -43,14 +39,15 @@ or you can save it immediately
43
39
 
44
40
  ```ruby
45
41
  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?
42
+ # => <#User id: 1, ...>
43
+ operation = UserCloner.call(user)
44
+ # => <#Clowne::Adapters::Sequel::Operation...>
45
+ operation.persist
46
+ # => true
47
+ cloned = operation.to_record
48
+ # => <#User id: 2, ...>
49
+ cloned.new?
53
50
  # => false
54
51
  ```
55
52
 
56
- If you try to clone associations without `NestedAttributes` plugin, Clowne will skip this declaration.
53
+ If you try to clone association without `NestedAttributes` plugin, Clowne will skip this declaration.
@@ -7,7 +7,7 @@ Clowne supports the following ORM adapters (and associations):
7
7
 
8
8
  Adapter |1:1 | 1:M | M:M |
9
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 |
10
+ [:active_record](/docs/active_record.html) | has_one | has_many | has_and_belongs_to_many |
11
+ [:sequel](/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
12
12
 
13
13
  For more information see the corresponding adapter documentation.
@@ -140,7 +140,7 @@ end
140
140
  Clowne automaticaly marks all specs in `spec/cloners` folder with `type: :cloner`. Otherwise you have to add this tag you.
141
141
 
142
142
 
143
- ## Clone actions matchers
143
+ ## Using partial cloning
144
144
 
145
145
  Under the hood, Clowne builds a [compilation plan](architecture.md) which is used to clone the record.
146
146
 
@@ -155,14 +155,14 @@ RSpec.describe UserCloner, type: :cloner do
155
155
 
156
156
  specify 'simple case' do
157
157
  # apply only the specified part of the plan
158
- cloned_user = described_class.partial_apply(:nullify, user)
158
+ cloned_user = described_class.partial_apply(:nullify, user).to_record
159
159
  expect(cloned_user.email).to be_nil
160
160
  # finalize wasn't applied
161
161
  expect(cloned_user.name).to eq 'Bombon'
162
162
  end
163
163
 
164
164
  specify 'with params' do
165
- cloned_user = described_class.partial_apply(:finalize, user, name: 'new name')
165
+ cloned_user = described_class.partial_apply(:finalize, user, name: 'new name').to_record
166
166
  # nullify actions were not applied!
167
167
  expect(cloned_user.email).to eq user.email
168
168
  # finalize was applied
@@ -171,7 +171,9 @@ RSpec.describe UserCloner, type: :cloner do
171
171
 
172
172
  specify 'with traits' do
173
173
  a_user = create(:user, name: 'Dindon')
174
- cloned_user = described_class.partial_apply(:init_as, user, traits: :copy, target: a_user)
174
+ cloned_user = described_class.partial_apply(
175
+ :init_as, user, traits: :copy, target: a_user
176
+ ).to_record
175
177
  # returned user is the same as target
176
178
  expect(cloned_user).to be_eql(a_user)
177
179
  expect(cloned_user.name).to eq 'Bombon'
@@ -186,7 +188,7 @@ RSpec.describe UserCloner, type: :cloner do
186
188
  # plan.apply(:association)
187
189
  cloned_user = described_class.partial_apply(
188
190
  'association.posts', user, traits: :with_popular_posts, min_rating: 1
189
- )
191
+ ).to_record
190
192
 
191
193
  expect(cloned_user.posts.size).to eq 1
192
194
  expect(cloned_user.posts.first.text).to eq 'Flying Dumplings'
@@ -0,0 +1,6 @@
1
+ # Clowne Docs
2
+
3
+ 1. Install `node` and `yarn`
4
+ 2. `$ yarn` - install frontend deps
5
+ 3. `$ yarn run start --port 3002`
6
+ 4. `$ open http://localhost:3002`
@@ -50,17 +50,13 @@ class Footer extends React.Component {
50
50
  </a>
51
51
  </div>
52
52
  </div>
53
+ */}
53
54
  <div className="footer--block">
54
55
  <h5>Resources</h5>
55
56
  <div className="footer--list--item">
56
- <a href="#" target="_blank">Super puper chronicles post</a>
57
+ <a href="https://evilmartians.com/chronicles/clowne-clone-ruby-models-with-a-smile" target="_blank">Clowne: clone Ruby models with a smile</a>
57
58
  </div>
58
- <div className="footer--list--item">
59
- <a href="#" target="_blank">
60
- Talk Slides
61
- </a>
62
- </div>
63
- </div> */}
59
+ </div>
64
60
  <div className="footer--block legals">
65
61
  <p className="footer--copy">
66
62
  <span className="copy">{yearLabel}</span>&nbsp;
@@ -77,10 +73,10 @@ class Footer extends React.Component {
77
73
  <div className="footer--humanoids">
78
74
  <div className="humanoids">
79
75
  <div className="humanoids__martian">
80
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#111" d="M26 88L8 78l18-10"/><path fill="#111" d="M94 92v-6H44c-5.5 0-10-4.5-10-10s4.5-10 10-10h50V32c0-14-7.9-22-22-22H48c-14.1 0-22 8-22 22v60h68z"/><circle fill="#FFF" cx="48" cy="50" r="8"/><circle fill="#FFF" cx="72" cy="50" r="8"/><circle fill="#363636" cx="48" cy="50" r="4"/><circle fill="#363636" cx="72" cy="50" r="4"/><g fill="#fff"><path d="M48 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM82 50c0 5.5-4.5 10-10 10s-10-4.5-10-10 4.5-10 10-10 10 4.5 10 10zm-16 0c0 3.3 2.7 6 6 6s6-2.7 6-6-2.7-6-6-6-6 2.7-6 6z"/><path d="M102 8c-3.8 0-6-2.2-6-6 0-1.1-.9-2-2-2s-2 .9-2 2c0 2.2.5 4.1 1.5 5.7L88.2 13c-4-3.3-9.5-5-16.2-5H48c-6.7 0-12.2 1.7-16.2 4.9l-5.3-5.3C27.5 6.1 28 4.2 28 2c0-1.1-.9-2-2-2s-2 .9-2 2c0 3.8-2.2 6-6 6-1.1 0-2 .9-2 2s.9 2 2 2c2.2 0 4.1-.5 5.7-1.5l5.3 5.3c-3.2 4-4.9 9.5-4.9 16.2v34.8L3.9 78 24 89v3h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H44c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8h-2l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-3.1 6.2C37.1 80.7 36 78.5 36 76c0-4.4 3.6-8 8-8l4 8 4-8h4l4 8 4-8h4l4 8 4-8h4l4 8 4-8h8V32c0-6.7-1.7-12.2-4.9-16.2l5.3-5.3c1.6 1 3.5 1.5 5.7 1.5 1.1 0 2-.9 2-2s-1-2-2.1-2zM24 84.4L12.1 78 24 71.4v13z"/></g></svg>
76
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#E2CBB5" d="M26 88L8 78l18-10"></path><path fill="#9FA628" d="M94 92v-6H44c-5.5 0-10-4.5-10-10s4.5-10 10-10h50V32c0-14-7.9-22-22-22H48c-14.1 0-22 8-22 22v60h68z"></path><circle fill="#FFF" cx="48" cy="50" r="8"></circle><circle fill="#FFF" cx="72" cy="50" r="8"></circle><circle fill="#BF6C35" cx="48" cy="50" r="4"></circle><circle fill="#BF6C35" cx="72" cy="50" r="4"></circle><g fill="#663F4C"><path d="M48 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM82 50c0 5.5-4.5 10-10 10s-10-4.5-10-10 4.5-10 10-10 10 4.5 10 10zm-16 0c0 3.3 2.7 6 6 6s6-2.7 6-6-2.7-6-6-6-6 2.7-6 6z"></path><path d="M102 8c-3.8 0-6-2.2-6-6 0-1.1-.9-2-2-2s-2 .9-2 2c0 2.2.5 4.1 1.5 5.7L88.2 13c-4-3.3-9.5-5-16.2-5H48c-6.7 0-12.2 1.7-16.2 4.9l-5.3-5.3C27.5 6.1 28 4.2 28 2c0-1.1-.9-2-2-2s-2 .9-2 2c0 3.8-2.2 6-6 6-1.1 0-2 .9-2 2s.9 2 2 2c2.2 0 4.1-.5 5.7-1.5l5.3 5.3c-3.2 4-4.9 9.5-4.9 16.2v34.8L3.9 78 24 89v3h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H44c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8h-2l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-3.1 6.2C37.1 80.7 36 78.5 36 76c0-4.4 3.6-8 8-8l4 8 4-8h4l4 8 4-8h4l4 8 4-8h4l4 8 4-8h8V32c0-6.7-1.7-12.2-4.9-16.2l5.3-5.3c1.6 1 3.5 1.5 5.7 1.5 1.1 0 2-.9 2-2s-1-2-2.1-2zM24 84.4L12.1 78 24 71.4v13z"></path></g></svg>
81
77
  </div>
82
78
  <div className="humanoids__human">
83
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#FFF" d="M14 62v30h68v-6H32c-5.5 0-10-4.5-10-10s4.5-10 10-10h50v-4c4 0 8-3.6 8-8s-4-8-8-8V32c0-14-7.9-22-22-22H36c-14.1 0-22 8-22 22v14c-4 0-8 3.6-8 8s4 8 8 8z"/><circle fill="#FFF" cx="36" cy="50" r="8"/><circle fill="#FFF" cx="60" cy="50" r="8"/><circle fill="#363636" cx="36" cy="50" r="4"/><circle fill="#363636" cx="60" cy="50" r="4"/><path fill="#363636" d="M60 10H36c-14.1 0-22 8-22 22v2l4-4 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 4 4v-2c0-14-7.9-22-22-22z"/><g fill="#111"><path d="M36 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM60 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6z"/><path d="M12 63.8V92h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H32c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8H74v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1c-4 0-8-3.6-8-8s3.6-8 8-8h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h4v-4.2c5-.9 8-5 8-9.8s-3-8.9-8-9.8V32c0-15.3-8.7-24-24-24H36c-15.3 0-24 8.7-24 24v12.2c-5 .9-8 5-8 9.8s3 8.9 8 9.8zm72-15.5c2 .8 4 3 4 5.7s-2 4.8-4 5.7V48.3zm-72 0v11.3c-2-.8-4-3-4-5.7s2-4.7 4-5.6z"/></g></svg>
79
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#E2CBB5" d="M14 62v30h68v-6H32c-5.5 0-10-4.5-10-10s4.5-10 10-10h50v-4c4 0 8-3.6 8-8s-4-8-8-8V32c0-14-7.9-22-22-22H36c-14.1 0-22 8-22 22v14c-4 0-8 3.6-8 8s4 8 8 8z"></path><circle fill="#FFF" cx="36" cy="50" r="8"></circle><circle fill="#FFF" cx="60" cy="50" r="8"></circle><circle fill="#9FA628" cx="36" cy="50" r="4"></circle><circle fill="#9FA628" cx="60" cy="50" r="4"></circle><path fill="#BF6C35" d="M60 10H36c-14.1 0-22 8-22 22v2l4-4 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 4 4v-2c0-14-7.9-22-22-22z"></path><g fill="#663F4C"><path d="M36 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM60 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6z"></path><path d="M12 63.8V92h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H32c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8H74v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1c-4 0-8-3.6-8-8s3.6-8 8-8h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h4v-4.2c5-.9 8-5 8-9.8s-3-8.9-8-9.8V32c0-15.3-8.7-24-24-24H36c-15.3 0-24 8.7-24 24v12.2c-5 .9-8 5-8 9.8s3 8.9 8 9.8zm72-15.5c2 .8 4 3 4 5.7s-2 4.8-4 5.7V48.3zm-72 0v11.3c-2-.8-4-3-4-5.7s2-4.7 4-5.6z"></path></g></svg>
84
80
  </div>
85
81
  </div>
86
82
  </div>
@@ -5,23 +5,26 @@
5
5
  "previous": "Previous",
6
6
  "tagline": "A flexible gem for cloning your models",
7
7
  "active_record": "Active Record",
8
+ "after_persist": "After Persist",
8
9
  "alternatives": "Motivation & Alternatives",
9
10
  "architecture": "Architecture",
10
11
  "basic_example": "Basic Example",
11
- "configuration": "Configuration",
12
+ "clone_mapper": "Clone mapper",
12
13
  "customization": "Customization",
13
14
  "exclude_association": "Exclude Association",
14
- "execution_order": "Execution Order",
15
15
  "finalize": "Finalization",
16
16
  "Finalize": "Finalize",
17
+ "from_v02_to_v10": "From v0.2.x to v1.0.0",
17
18
  "implicit_cloner": "Implicit Cloner",
18
19
  "include_association": "Include Association",
19
20
  "init_as": "Initialize Cloning Target",
20
21
  "Init As": "Init As",
21
22
  "inline_configuration": "Inline Configuration",
22
- "installation": "Installation",
23
+ "installation": "Installation & Configuration",
23
24
  "nullify": "Nullify Attributes",
24
25
  "Nullify": "Nullify",
26
+ "operation": "Operation",
27
+ "overview": "Overview",
25
28
  "parameters": "Parameters",
26
29
  "sequel": "Sequel",
27
30
  "supported_adapters": "Supported Adapters",
@@ -126,7 +129,8 @@
126
129
  "Getting Started": "Getting Started",
127
130
  "API": "API",
128
131
  "Adapters": "Adapters",
129
- "Features": "Features"
132
+ "Advanced Options": "Advanced Options",
133
+ "Upgrade Notes": "Upgrade Notes"
130
134
  },
131
135
  "pages-strings": {
132
136
  "Help Translate|recruit community translators for your project": "Help Translate",