clowne 1.3.0 → 1.5.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -1
  3. data/README.md +1 -4
  4. data/lib/clowne/adapters/active_record/associations/belongs_to.rb +0 -1
  5. data/lib/clowne/adapters/active_record/associations/has_one.rb +0 -1
  6. data/lib/clowne/adapters/active_record/associations/noop.rb +1 -1
  7. data/lib/clowne/adapters/active_record/associations.rb +1 -1
  8. data/lib/clowne/adapters/active_record/resolvers/association.rb +0 -1
  9. data/lib/clowne/adapters/base/association.rb +1 -1
  10. data/lib/clowne/adapters/base.rb +2 -2
  11. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +0 -4
  12. data/lib/clowne/adapters/sequel/associations.rb +1 -1
  13. data/lib/clowne/adapters/sequel/resolvers/association.rb +0 -1
  14. data/lib/clowne/cloner.rb +1 -1
  15. data/lib/clowne/declarations/trait.rb +1 -1
  16. data/lib/clowne/declarations.rb +1 -1
  17. data/lib/clowne/ext/record_key.rb +1 -1
  18. data/lib/clowne/resolvers/after_clone.rb +2 -1
  19. data/lib/clowne/resolvers/init_as.rb +0 -1
  20. data/lib/clowne/rspec/clone_association.rb +0 -1
  21. data/lib/clowne/version.rb +1 -1
  22. data/lib/clowne.rb +2 -3
  23. metadata +7 -110
  24. data/.codeclimate.yml +0 -7
  25. data/.gitattributes +0 -1
  26. data/.gitignore +0 -16
  27. data/.rspec +0 -3
  28. data/.rubocop.yml +0 -28
  29. data/.rufo +0 -3
  30. data/.travis.yml +0 -45
  31. data/Gemfile +0 -20
  32. data/Rakefile +0 -8
  33. data/bin/console +0 -14
  34. data/bin/setup +0 -8
  35. data/clowne.gemspec +0 -36
  36. data/docs/.nojekyll +0 -0
  37. data/docs/.rubocop.yml +0 -18
  38. data/docs/CNAME +0 -1
  39. data/docs/README.md +0 -131
  40. data/docs/_sidebar.md +0 -25
  41. data/docs/active_record.md +0 -33
  42. data/docs/after_clone.md +0 -53
  43. data/docs/after_persist.md +0 -77
  44. data/docs/architecture.md +0 -138
  45. data/docs/assets/docsify.min.js +0 -1
  46. data/docs/assets/prism-ruby.min.js +0 -1
  47. data/docs/assets/styles.css +0 -348
  48. data/docs/assets/vue.css +0 -1
  49. data/docs/clone_mapper.md +0 -59
  50. data/docs/customization.md +0 -63
  51. data/docs/exclude_association.md +0 -61
  52. data/docs/finalize.md +0 -31
  53. data/docs/from_v02_to_v1.md +0 -83
  54. data/docs/getting_started.md +0 -171
  55. data/docs/implicit_cloner.md +0 -33
  56. data/docs/include_association.md +0 -133
  57. data/docs/index.html +0 -29
  58. data/docs/init_as.md +0 -40
  59. data/docs/inline_configuration.md +0 -37
  60. data/docs/nullify.md +0 -33
  61. data/docs/operation.md +0 -55
  62. data/docs/parameters.md +0 -112
  63. data/docs/sequel.md +0 -50
  64. data/docs/supported_adapters.md +0 -10
  65. data/docs/testing.md +0 -194
  66. data/docs/traits.md +0 -25
  67. data/gemfiles/activerecord42.gemfile +0 -9
  68. data/gemfiles/jruby.gemfile +0 -10
  69. data/gemfiles/railsmaster.gemfile +0 -10
  70. data/lib/clowne/ext/yield_self_then.rb +0 -25
data/docs/nullify.md DELETED
@@ -1,33 +0,0 @@
1
- # Nullify Attributes
2
-
3
- To set a bunch of attributes to `nil` you can use the `nullify` declaration:
4
-
5
- ```ruby
6
- class User < ActiveRecord::Base
7
- # t.string :name
8
- # t.string :surname
9
- # t.string :email
10
- end
11
-
12
- class UserCloner < Clowne::Cloner
13
- nullify :name, :email
14
-
15
- trait :nullify_surname do
16
- nullify :surname
17
- end
18
- end
19
-
20
- clone = UserCloner.call(user).to_record
21
- clone.name.nil?
22
- # => true
23
- clone.email.nil?
24
- # => true
25
- clone.surname.nil?
26
- # => false
27
-
28
- clone2 = UserCloner.call(user, traits: :nullify_surname).to_record
29
- clone2.name.nil?
30
- # => true
31
- clone2.surname.nil?
32
- # => true
33
- ```
data/docs/operation.md DELETED
@@ -1,55 +0,0 @@
1
- # Operation
2
-
3
- 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`.
4
-
5
- An instance of `Operation` has a very clear interface:
6
-
7
- ```ruby
8
- class User < ActiveRecord::Base; end
9
-
10
- class UserCloner < Clowne::Cloner
11
- nullify :email
12
-
13
- after_persist do |_origin, cloned, **|
14
- cloned.update(email: "evl-#{cloned.id}.ms")
15
- end
16
- end
17
-
18
- user = User.create(email: "evl.ms")
19
- # => <#User id: 1, email: 'evl.ms', ...>
20
-
21
- operation = UserCloner.call(user)
22
-
23
- # Return resulted (non saved) object:
24
- operation.to_record
25
- # => <#User id: nil, email: nil, ...>
26
-
27
- # Save cloned object and call after_persist callbacks:
28
- operation.persist # or operation.persist!
29
- # => true
30
-
31
- operation.to_record
32
- # => <#User id: 2, email: 'evl-2.ms', ...>
33
-
34
- # Call only after_persist callbacks:
35
- user2 = operation.to_record
36
- # => <#User id: 2, email: 'evl-2.ms', ...>
37
- user2.update(email: "admin@example.com")
38
- # => <#User id: 2, email: 'admin@example.com' ...>
39
- operation.run_after_persist
40
- # => <#User id: 2, email: 'evl-2.ms', ...>
41
- ```
42
-
43
- The last example is weird, but it can be helpful when you need to execute `save` (or `save!`) separately from `after_persist` callbacks:
44
-
45
- ```ruby
46
- operation = UserClone.call(user)
47
-
48
- # Wrap main cloning into the transaction
49
- ActiveRecord::Base.transaction do
50
- operation.to_record.save!
51
- end
52
-
53
- # And after that execute after_persist without transaction
54
- operation.run_after_persist
55
- ```
data/docs/parameters.md DELETED
@@ -1,112 +0,0 @@
1
- # Parameters
2
-
3
- Clowne provides parameters for make your cloning logic more flexible. You can see their using in [`include_association`](include_association.md#scope) and [`finalize`](finalize.md) documentation pages.
4
-
5
- Example:
6
-
7
- ```ruby
8
- class UserCloner < Clowne::Cloner
9
- include_association :posts, ->(params) { where(state: params[:state]) }
10
-
11
- finalize do |_source, record, **params|
12
- record.email = params[:email]
13
- end
14
- end
15
-
16
- operation = UserCloner.call(user, state: :draft, email: "cloned@example.com")
17
- cloned = operation.to_record
18
- cloned.email
19
- # => 'cloned@example.com'
20
- ```
21
-
22
- ## Potential Problems
23
-
24
- Clowne is born as a part of our big project and we use it for cloning really deep object relations. When we started to use params and forwarding them between parent-child cloners we got a nasty bugs.
25
-
26
- As result we strongly recommend to use ruby keyword arguments instead of params hash:
27
-
28
- ```ruby
29
- # Bad
30
- finalize do |_source, record, **params|
31
- record.email = params[:email]
32
- end
33
-
34
- # Good
35
- finalize do |_source, record, email:, **|
36
- record.email = email
37
- end
38
- ```
39
-
40
- ## Nested Parameters
41
-
42
- Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/clowne-rb/clowne/issues/15)).
43
-
44
- Let's explain what the difference:
45
-
46
- ```ruby
47
- class UserCloner < Clowne::Cloner
48
- # Don't pass parameters to associations
49
- trait :default do
50
- include_association :profile
51
- # equal to include_association :profile, params: false
52
- end
53
-
54
- # Pass all parameters to associations
55
- trait :all_params do
56
- include_association :profile, params: true
57
- end
58
-
59
- # Filter parameters by key.
60
- # Notice: value by key must be a Hash.
61
-
62
- trait :by_key do
63
- include_association :profile, params: :profile
64
- end
65
-
66
- # Execute custom block with params as argument
67
- trait :by_block do
68
- include_association :profile, params: Proc.new do |params|
69
- params[:profile].map { |k, v| [k, v.upcase] }.to_h
70
- end
71
- end
72
-
73
- # Execute custom block with params and parent record as arguments
74
- trait :by_block_with_parent do
75
- include_association :profile, params: Proc.new do |params, user|
76
- {
77
- name: params[:profile][:name],
78
- email: user.email,
79
- }
80
- end
81
- end
82
- end
83
-
84
- class ProfileCloner < Clowne::Cloner
85
- finalize do |_source, record, **params|
86
- record.jsonb_field = params
87
- end
88
- end
89
-
90
- # Execute:
91
-
92
- def get_profile_jsonb(user, trait)
93
- params = {profile: {name: "John", surname: "Cena"}}
94
- cloned = UserCloner.call(user, traits: trait, **params).to_record
95
- cloned.profile.jsonb_field
96
- end
97
-
98
- get_profile_jsonb(user, :default)
99
- # => {}
100
-
101
- get_profile_jsonb(user, :all_params)
102
- # => { profile: { name: 'John', surname: 'Cena' } }
103
-
104
- get_profile_jsonb(user, :by_key)
105
- # => { name: 'John', surname: 'Cena' }
106
-
107
- get_profile_jsonb(user, :by_block)
108
- # => { name: 'JOHN', surname: 'CENA' }
109
-
110
- get_profile_jsonb(user, :by_block_with_parent)
111
- # => { name: 'JOHN', email: user.email }
112
- ```
data/docs/sequel.md DELETED
@@ -1,50 +0,0 @@
1
- # Sequel
2
-
3
- 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.
4
-
5
- Example:
6
-
7
- ```ruby
8
- class UserCloner < Clowne::Cloner
9
- adapter :sequel
10
-
11
- include_association :account
12
- end
13
-
14
- class User < Sequel::Model
15
- # Configure NestedAttributes plugin
16
- plugin :nested_attributes
17
-
18
- one_to_one :account
19
- nested_attributes :account
20
- end
21
- ```
22
-
23
- and get cloned user
24
-
25
- ```ruby
26
- user = User.last
27
- operation = UserCloner.call(user)
28
- # => <#Clowne::Adapters::Sequel::Operation...>
29
- cloned = operation.to_record
30
- # => <#User id: nil, ...>
31
- cloned.new?
32
- # => true
33
- ```
34
-
35
- or you can save it immediately
36
-
37
- ```ruby
38
- user = User.last
39
- # => <#User id: 1, ...>
40
- operation = UserCloner.call(user)
41
- # => <#Clowne::Adapters::Sequel::Operation...>
42
- operation.persist
43
- # => true
44
- cloned = operation.to_record
45
- # => <#User id: 2, ...>
46
- cloned.new?
47
- # => false
48
- ```
49
-
50
- If you try to clone association without `NestedAttributes` plugin, Clowne will skip this declaration.
@@ -1,10 +0,0 @@
1
- # Supported Adapters
2
-
3
- Clowne supports the following ORM adapters (and associations):
4
-
5
- Adapter |1:1 | 1:M | M:M |
6
- ---------------------------------------------------|------------|-------------|-------------------------|
7
- [:active_record](active_record) | has_one | has_many | has_and_belongs_to_many |
8
- [:sequel](sequel) | one_to_one | one_to_many | many_to_many |
9
-
10
- For more information see the corresponding adapter documentation.
data/docs/testing.md DELETED
@@ -1,194 +0,0 @@
1
- # Testing
2
-
3
- Clowne provides specific tools to help you test your cloners.
4
-
5
- The main goal is to make it possible to test different cloning phases separately and avoid _heavy_ tests setup phases.
6
-
7
- Let's consider the following models and cloners:
8
-
9
- ```ruby
10
- # app/models/user.rb
11
- class User < ApplicationRecord
12
- has_one :profile
13
- has_many :posts
14
- end
15
-
16
- # app/models/post.rb
17
- class Post < ApplicationRecord
18
- has_many :comments
19
- has_many :votes
20
-
21
- scope :draft, -> { where(draft: true) }
22
- end
23
-
24
- # app/cloners/user_cloner.rb
25
- class UserCloner < Clowne::Cloner
26
- class ProfileCloner
27
- nullify :rating
28
- end
29
-
30
- include_association :profile, clone_with: ProfileCloner
31
-
32
- nullify :email
33
-
34
- finalize do |_, record, name: nil, **|
35
- record.name = name unless name.nil?
36
- end
37
-
38
- trait :copy do
39
- init_as do |user, target:, **|
40
- # copy name
41
- target.name = user.name
42
- target
43
- end
44
- end
45
-
46
- trait :with_posts do
47
- include_association :posts, :draft, traits: :mark_as_copy
48
- end
49
-
50
- trait :with_popular_posts do
51
- include_association :posts, (lambda do |params|
52
- where("rating > ?", params[:min_rating])
53
- end)
54
- end
55
- end
56
-
57
- # app/cloners/post_cloner.rb
58
- class PostCloner < Clowne::Cloner
59
- include_association :comments
60
-
61
- trait :mark_as_copy do |_, record|
62
- record.title += " (copy)"
63
- end
64
- end
65
- ```
66
-
67
- ## Getting started
68
-
69
- Currently, only [RSpec](http://rspec.info/) is supported.
70
-
71
- Add this line to your `spec_helper.rb` (or `rails_helper.rb`):
72
-
73
- ```ruby
74
- require "clowne/rspec"
75
- ```
76
-
77
- ## Configuration matchers
78
-
79
- There are several matchers that allow you to verify the cloner configuration.
80
-
81
- ### `clone_associations`
82
-
83
- This matcher vefifies that your cloner includes the specified associations:
84
-
85
- ```ruby
86
- # spec/cloners/user_cloner_spec.rb
87
- RSpec.describe UserCloner, type: :cloner do
88
- subject { described_class }
89
-
90
- specify do
91
- # checks that only the specified associations is included
92
- is_expected.to clone_associations(:profile)
93
-
94
- # with traits
95
- is_expected.to clone_associations(:profile, :posts)
96
- .with_traits(:with_posts)
97
-
98
- # raises when there are some unspecified associations
99
- is_expected.to clone_associations(:profile)
100
- .with_traits(:with_posts)
101
- #=> raises RSpec::Expectations::ExpectationNotMetError
102
- end
103
- end
104
- ```
105
-
106
- ### `clone_association`
107
-
108
- This matcher allows to verify the specified association options:
109
-
110
- ```ruby
111
- # spec/cloners/user_cloner_spec.rb
112
- RSpec.describe UserCloner, type: :cloner do
113
- subject { described_class }
114
-
115
- specify do
116
- # simply check that association is included
117
- is_expected.to clone_association(:profile)
118
-
119
- # check options
120
- is_expected.to clone_association(
121
- :profile,
122
- clone_with: described_class::ProfileCloner
123
- )
124
-
125
- # with traits, scope and activated trait
126
- is_expected.to clone_association(
127
- :posts,
128
- traits: :mark_as_copy,
129
- scope: :draft
130
- ).with_traits(:with_posts)
131
- end
132
- end
133
- ```
134
-
135
- **NOTE:** `clone_associations`/`clone_association` matchers are only available in groups marked with `type: :cloner` tag.
136
-
137
- Clowne automaticaly marks all specs in `spec/cloners` folder with `type: :cloner`. Otherwise you have to add this tag you.
138
-
139
-
140
- ## Using partial cloning
141
-
142
- Under the hood, Clowne builds a [compilation plan](architecture.md) which is used to clone the record.
143
-
144
- Plan is a set of _actions_ (such as `nullify`, `finalize`, `association`, `init_as`) which are applied to the record.
145
-
146
- Most of the time these actions don't depend on each other, thus we can test them separately:
147
-
148
- ```ruby
149
- # spec/cloners/user_cloner_spec.rb
150
- RSpec.describe UserCloner, type: :cloner do
151
- subject(:user) { create :user, name: "Bombon" }
152
-
153
- specify "simple case" do
154
- # apply only the specified part of the plan
155
- cloned_user = described_class.partial_apply(:nullify, user).to_record
156
- expect(cloned_user.email).to be_nil
157
- # finalize wasn't applied
158
- expect(cloned_user.name).to eq "Bombon"
159
- end
160
-
161
- specify "with params" do
162
- cloned_user = described_class.partial_apply(:finalize, user, name: "new name").to_record
163
- # nullify actions were not applied!
164
- expect(cloned_user.email).to eq user.email
165
- # finalize was applied
166
- expect(cloned_user.name).to eq "new name"
167
- end
168
-
169
- specify "with traits" do
170
- a_user = create(:user, name: "Dindon")
171
- cloned_user = described_class.partial_apply(
172
- :init_as, user, traits: :copy, target: a_user
173
- ).to_record
174
- # returned user is the same as target
175
- expect(cloned_user).to be_eql(a_user)
176
- expect(cloned_user.name).to eq "Bombon"
177
- end
178
-
179
- specify "associations" do
180
- create(:post, user: user, rating: 1, text: "Boom Boom")
181
- create(:post, user: user, rating: 2, text: "Flying Dumplings")
182
-
183
- # you can specify which associations to include (you can use array)
184
- # to apply all associations write:
185
- # plan.apply(:association)
186
- cloned_user = described_class.partial_apply(
187
- "association.posts", user, traits: :with_popular_posts, min_rating: 1
188
- ).to_record
189
-
190
- expect(cloned_user.posts.size).to eq 1
191
- expect(cloned_user.posts.first.text).to eq "Flying Dumplings"
192
- end
193
- end
194
- ```
data/docs/traits.md DELETED
@@ -1,25 +0,0 @@
1
- # Traits
2
-
3
- Traits allow you to group cloner declarations together and then apply them (like in [`factory_bot`](https://github.com/thoughtbot/factory_bot)):
4
-
5
- ```ruby
6
- class UserCloner < Clowne::Cloner
7
- trait :with_posts do
8
- include_association :posts
9
- end
10
-
11
- trait :with_profile do
12
- include_association :profile
13
- end
14
-
15
- trait :nullify_name do
16
- nullify :name
17
- end
18
- end
19
-
20
- UserCloner.call(user, traits: %i[with_posts with_profile nullify_name])
21
- # or
22
- UserCloner.call(user, traits: :nullify_name)
23
- # or
24
- # ...
25
- ```
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.2"
6
- gem "sequel", ">= 5.0"
7
- gem "sqlite3", "~> 1.3.13"
8
-
9
- gemspec path: ".."
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord-jdbcsqlite3-adapter", "~> 50.0"
6
- gem "jdbc-sqlite3"
7
- gem "activerecord", "~> 5.0.0"
8
- gem "sequel", ">= 5.0"
9
-
10
- gemspec path: ".."
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "arel", github: "rails/arel"
6
- gem "rails", github: "rails/rails"
7
- gem "sequel", github: "jeremyevans/sequel"
8
- gem "sqlite3"
9
-
10
- gemspec path: ".."
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Clowne
4
- module Ext # :nodoc: all
5
- # Add yield_self and then if missing
6
- module YieldSelfThen
7
- module Ext
8
- unless nil.respond_to?(:yield_self)
9
- def yield_self
10
- yield self
11
- end
12
- end
13
-
14
- alias then yield_self
15
- end
16
-
17
- # See https://github.com/jruby/jruby/issues/5220
18
- ::Object.include(Ext) if RUBY_PLATFORM.match?(/java/i)
19
-
20
- refine Object do
21
- include Ext
22
- end
23
- end
24
- end
25
- end