clowne 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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