clowne 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1b3c79ac97f903ed3f2c1f8ae8b894dd7c4f3c4da5ab28350e59482ef08714e
4
- data.tar.gz: af3aa849d2ba1dfdc34f8edbb612c384bafefd21d2ef74f2842e659af4690867
3
+ metadata.gz: 50a2126fd3d4f4581bb9d06a1d53489dab431e2285683cba815df66a683d154a
4
+ data.tar.gz: 973bebd26c7e8e33807d6a4a3438d5f72aefb540bbbf4ce4d5aecfcf58761893
5
5
  SHA512:
6
- metadata.gz: bbf985fe91770d1d34ae133492ba58aadc7eeab15b3dcc6f122db94d6e48e42dd8c885c4b145da2325583a98143d28464b128c7f34174a13f4922a72a340cfff
7
- data.tar.gz: f47be4fac65591d29754756e2dd59a002d1eec0fcbfd661d2619f113020d97a7f1630d0d738cb3d2c9a7496d4302e2a0e888a3d537c9dcb2fed0053db72ee1db
6
+ metadata.gz: db3a1a9e38b180a2a5ad6cd490fe9852f074de4484df91ff62525fb88497a2785ef9307745a264bcb082a42ff9f1be95848534a949a78c96e591141c24fefea1
7
+ data.tar.gz: 565def47871791088a954d8ffd7ea374de91d37844fcc3bed97f35c6e299d65bd14f3c817906815b79544a7080a456058ed46dbf375d5d200ba7956b79149ac3
@@ -1,5 +1,11 @@
1
1
  # Change log
2
2
 
3
+ ## 1.1.0 (2019-03-20)
4
+
5
+
6
+ - Add `after_clone` declaration. ([@elardo][])
7
+ - Add opporotunity to include belongs_to association for active_record adapter. ([@madding][])
8
+
3
9
  ## 1.0.0 (2019-02-26)
4
10
 
5
11
  - Return `Operation` instance as a rusult of cloning. ([@ssnickolay][])
@@ -36,3 +42,5 @@ See [migration guide](https://clowne.evilmartians.io/docs/from_v02_to_v10.html)
36
42
 
37
43
  [@palkan]: https://github.com/palkan
38
44
  [@ssnickolay]: https://github.com/ssnickolay
45
+ [@elardo]: https://github.com/elardo
46
+ [@madding]: https://github.com/madding
data/README.md CHANGED
@@ -114,10 +114,10 @@ Take a look at our [documentation](https://clowne.evilmartians.io) for more info
114
114
 
115
115
  ### Supported ORM adapters
116
116
 
117
- Adapter |1:1 | 1:M | M:M |
118
- ------------------------------------------|------------|-------------|-------------------------|
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 |
117
+ Adapter |1:1 |*:1 | 1:M | M:M |
118
+ ------------------------------------------|------------|------------|-------------|-------------------------|
119
+ [Active Record](https://clowne.evilmartians.io/clowne/docs/active_record.html) | has_one | belongs_to | 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 |
121
121
 
122
122
  ## Maintainers
123
123
 
@@ -0,0 +1,56 @@
1
+ ---
2
+ id: after_clone
3
+ title: After Clone
4
+ ---
5
+
6
+ The `after_clone` callbacks can help you to make additional operations on cloned record, like checking it with some business logic or actualizing cloned record attributes, before it will be saved to the database. Also it can help to avoid unneeded usage of [`after_persist`](after_persist.md) callbacks, and additional queries to database.
7
+
8
+ Examples:
9
+
10
+ ```ruby
11
+ class User < ActiveRecord::Base
12
+ # create_table :users do |t|
13
+ # t.string :login
14
+ # t.integer :draft_count
15
+ # end
16
+
17
+ has_many :posts # all user's posts
18
+ end
19
+
20
+ class Post < ActiveRecord::Base
21
+ # create_table :posts do |t|
22
+ # t.integer :user_id
23
+ # t.boolean :is_draft
24
+ # end
25
+
26
+ scope :draft, -> { where is_draft: true }
27
+ end
28
+
29
+ class UserCloner < Clowne::Cloner
30
+ # clone user and his posts, which is drafts
31
+ include_association :posts, scope: :draft
32
+
33
+ after_clone do |_origin, clone, **|
34
+ # actualize user attribute
35
+ clone.draft_count = clone.posts.count
36
+ end
37
+ end
38
+ ```
39
+
40
+ `after_clone` runs when you call `Operation#to_record` or [`Operation#persist`]('operation.md) (or `Operation#persist!`)
41
+
42
+ ```ruby
43
+ # prepare data
44
+ user = User.create
45
+ 3.times { Post.create(user: user, is_draft: false) }
46
+ 2.times { Post.create(user: user, is_draft: true) }
47
+
48
+ operation = UserCloner.call(user)
49
+ # => <#Clowne::Utils::Operation ...>
50
+
51
+ clone = operation.to_record
52
+ # => <#User id: nil, draft_count: 2 ...>
53
+
54
+ clone.draft_count == user.posts.draft.count
55
+ # => true
56
+ ```
@@ -23,6 +23,13 @@ The declaration supports additional arguments:
23
23
  include_association name, scope, options
24
24
  ```
25
25
 
26
+ ### Supported Associations
27
+
28
+ Adapter |1:1 |*:1 | 1:M | M:M |
29
+ ------------------------------------------|------------|------------|-------------|-------------------------|
30
+ [Active Record](https://clowne.evilmartians.io/clowne/docs/active_record.html) | has_one | belongs_to | has_many | has_and_belongs_to|
31
+ [Sequel](https://clowne.evilmartians.io/clowne/docs/sequel.html) | one_to_one | - | one_to_many | many_to_many |
32
+
26
33
  ## Scope
27
34
 
28
35
  Scope can be a:
@@ -121,3 +128,9 @@ end
121
128
  ```
122
129
 
123
130
  **NOTE:** in that case, it's not possible to provide custom scopes and options.
131
+
132
+ ### Belongs To association
133
+
134
+ You can include belongs_to association, but will do it carefully.
135
+ If you have loop by relations in your models, when you clone it will raise SystemStackError.
136
+ Check this [test](https://github.com/palkan/clowne/blob/master/spec/clowne/integrations/active_record_belongs_to_spec.rb) for instance.
@@ -20,5 +20,6 @@ All built-in adapters have the same order and what happens when you call `Operat
20
20
  - [`clone associations`](include_association.md)
21
21
  - [`nullify`](nullify.md) attributes
22
22
  - run [`finalize`](finalize.md) blocks. _The order of [`finalize`](finalize.md) blocks is the order they've been written._
23
+ - run [`after_clone`](after_clone.md) callbacks
23
24
  - __SAVE CLONED RECORD__
24
25
  - run [`after_persist`](after_persist.md) callbacks
@@ -5,6 +5,7 @@
5
5
  "previous": "Previous",
6
6
  "tagline": "A flexible gem for cloning your models",
7
7
  "active_record": "Active Record",
8
+ "after_clone": "After Clone",
8
9
  "after_persist": "After Persist",
9
10
  "alternatives": "Motivation & Alternatives",
10
11
  "architecture": "Architecture",
@@ -12,6 +12,7 @@
12
12
  "exclude_association",
13
13
  "nullify",
14
14
  "finalize",
15
+ "after_clone",
15
16
  "after_persist",
16
17
  "init_as",
17
18
  "traits",
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'clowne/adapters/active_record/associations/base'
4
4
  require 'clowne/adapters/active_record/associations/noop'
5
+ require 'clowne/adapters/active_record/associations/belongs_to'
5
6
  require 'clowne/adapters/active_record/associations/has_one'
6
7
  require 'clowne/adapters/active_record/associations/has_many'
7
8
  require 'clowne/adapters/active_record/associations/has_and_belongs_to_many'
@@ -11,6 +12,7 @@ module Clowne
11
12
  class ActiveRecord
12
13
  module Associations
13
14
  AR_2_CLONER = {
15
+ belongs_to: BelongsTo,
14
16
  has_one: HasOne,
15
17
  has_many: HasMany,
16
18
  has_and_belongs_to_many: HABTM
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class ActiveRecord
6
+ module Associations
7
+ class BelongsTo < Base
8
+ # rubocop: disable Metrics/MethodLength
9
+ def call(record)
10
+ child = association
11
+ return record unless child
12
+
13
+ unless declaration.scope.nil?
14
+ warn(
15
+ '[Clowne] Belongs to association does not support scopes ' \
16
+ "(#{@association_name} for #{@source.class})"
17
+ )
18
+ end
19
+
20
+ child_clone = clone_one(child)
21
+ record.__send__(:"#{association_name}=", child_clone)
22
+ record
23
+ end
24
+ # rubocop: enable Metrics/MethodLength
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,6 +6,7 @@ require 'clowne/resolvers/init_as'
6
6
  require 'clowne/resolvers/nullify'
7
7
  require 'clowne/resolvers/finalize'
8
8
  require 'clowne/resolvers/after_persist'
9
+ require 'clowne/resolvers/after_clone'
9
10
 
10
11
  module Clowne
11
12
  module Adapters
@@ -70,6 +71,11 @@ Clowne::Adapters::Base.register_resolver(
70
71
  )
71
72
 
72
73
  Clowne::Adapters::Base.register_resolver(
73
- :after_persist, Clowne::Resolvers::AfterPersist,
74
+ :after_clone, Clowne::Resolvers::AfterClone,
74
75
  after: :finalize
75
76
  )
77
+
78
+ Clowne::Adapters::Base.register_resolver(
79
+ :after_persist, Clowne::Resolvers::AfterPersist,
80
+ after: :after_clone
81
+ )
@@ -24,7 +24,9 @@ module Clowne
24
24
  def to_record
25
25
  return @_record if defined?(@_record)
26
26
 
27
- @_record = @records[key(@clone)].to_model
27
+ @_record = @records[key(@clone)].to_model.tap do
28
+ run_after_clone
29
+ end
28
30
  end
29
31
  end
30
32
  end
@@ -31,3 +31,4 @@ require 'clowne/declarations/include_association'
31
31
  require 'clowne/declarations/nullify'
32
32
  require 'clowne/declarations/trait'
33
33
  require 'clowne/declarations/after_persist'
34
+ require 'clowne/declarations/after_clone'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Declarations
5
+ class AfterClone < Base # :nodoc: all
6
+ attr_reader :block
7
+
8
+ def initialize
9
+ raise ArgumentError, 'Block is required for after_clone' unless block_given?
10
+
11
+ @block = Proc.new
12
+ end
13
+
14
+ def compile(plan)
15
+ plan.add(:after_clone, self)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Clowne::Declarations.add :after_clone, Clowne::Declarations::AfterClone
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ class Resolvers
5
+ module AfterClone # :nodoc: all
6
+ def self.call(source, record, declaration, params:, **_options)
7
+ operation = Clowne::Utils::Operation.current
8
+ operation.add_after_clone(
9
+ proc do
10
+ declaration.block.call(source, record, params)
11
+ end
12
+ )
13
+ record
14
+ end
15
+ end
16
+ end
17
+ end
@@ -35,12 +35,17 @@ module Clowne
35
35
  attr_reader :mapper
36
36
 
37
37
  def initialize(mapper)
38
- @blocks = []
38
+ @after_clone_blocks = []
39
+ @after_persist_blocks = []
39
40
  @mapper = mapper
40
41
  end
41
42
 
42
43
  def add_after_persist(block)
43
- @blocks.unshift(block)
44
+ @after_persist_blocks.unshift(block)
45
+ end
46
+
47
+ def add_after_clone(block)
48
+ @after_clone_blocks.unshift(block)
44
49
  end
45
50
 
46
51
  def add_mapping(origin, clone)
@@ -48,7 +53,10 @@ module Clowne
48
53
  end
49
54
 
50
55
  def to_record
51
- @clone
56
+ return @_record if defined?(@_record)
57
+
58
+ run_after_clone
59
+ @_record = @clone
52
60
  end
53
61
 
54
62
  def persist!
@@ -76,7 +84,11 @@ module Clowne
76
84
  end
77
85
 
78
86
  def run_after_persist
79
- @blocks.each(&:call)
87
+ @after_persist_blocks.each(&:call)
88
+ end
89
+
90
+ def run_after_clone
91
+ @after_clone_blocks.each(&:call)
80
92
  end
81
93
  end
82
94
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clowne
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clowne
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-02-26 00:00:00.000000000 Z
12
+ date: 2019-03-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -134,6 +134,7 @@ files:
134
134
  - clowne.gemspec
135
135
  - docs/.rubocop.yml
136
136
  - docs/active_record.md
137
+ - docs/after_clone.md
137
138
  - docs/after_persist.md
138
139
  - docs/alternatives.md
139
140
  - docs/architecture.md
@@ -178,6 +179,7 @@ files:
178
179
  - lib/clowne/adapters/active_record.rb
179
180
  - lib/clowne/adapters/active_record/associations.rb
180
181
  - lib/clowne/adapters/active_record/associations/base.rb
182
+ - lib/clowne/adapters/active_record/associations/belongs_to.rb
181
183
  - lib/clowne/adapters/active_record/associations/has_and_belongs_to_many.rb
182
184
  - lib/clowne/adapters/active_record/associations/has_many.rb
183
185
  - lib/clowne/adapters/active_record/associations/has_one.rb
@@ -202,6 +204,7 @@ files:
202
204
  - lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb
203
205
  - lib/clowne/cloner.rb
204
206
  - lib/clowne/declarations.rb
207
+ - lib/clowne/declarations/after_clone.rb
205
208
  - lib/clowne/declarations/after_persist.rb
206
209
  - lib/clowne/declarations/base.rb
207
210
  - lib/clowne/declarations/exclude_association.rb
@@ -217,6 +220,7 @@ files:
217
220
  - lib/clowne/ext/string_constantize.rb
218
221
  - lib/clowne/ext/yield_self_then.rb
219
222
  - lib/clowne/planner.rb
223
+ - lib/clowne/resolvers/after_clone.rb
220
224
  - lib/clowne/resolvers/after_persist.rb
221
225
  - lib/clowne/resolvers/finalize.rb
222
226
  - lib/clowne/resolvers/init_as.rb