clowne 0.1.0.pre1 → 0.1.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +7 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +17 -0
  6. data/.travis.yml +15 -2
  7. data/CHANGELOG.md +9 -2
  8. data/Gemfile +1 -0
  9. data/README.md +25 -381
  10. data/clowne.gemspec +1 -0
  11. data/docs/.rubocop.yml +12 -0
  12. data/docs/active_record.md +36 -0
  13. data/docs/alternatives.md +26 -0
  14. data/docs/architecture.md +141 -0
  15. data/docs/basic_example.md +66 -0
  16. data/docs/configuration.md +29 -0
  17. data/docs/customization.md +64 -0
  18. data/docs/exclude_association.md +63 -0
  19. data/docs/execution_order.md +14 -0
  20. data/docs/finalize.md +35 -0
  21. data/docs/implicit_cloner.md +36 -0
  22. data/docs/include_association.md +119 -0
  23. data/docs/init_as.md +36 -0
  24. data/docs/inline_configuration.md +38 -0
  25. data/docs/installation.md +16 -0
  26. data/docs/nullify.md +37 -0
  27. data/docs/sequel.md +56 -0
  28. data/docs/supported_adapters.md +13 -0
  29. data/docs/traits.md +28 -0
  30. data/docs/web/.gitignore +11 -0
  31. data/docs/web/core/Footer.js +92 -0
  32. data/docs/web/i18n/en.json +134 -0
  33. data/docs/web/package.json +14 -0
  34. data/docs/web/pages/en/help.js +50 -0
  35. data/docs/web/pages/en/index.js +231 -0
  36. data/docs/web/pages/en/users.js +47 -0
  37. data/docs/web/sidebars.json +30 -0
  38. data/docs/web/siteConfig.js +44 -0
  39. data/docs/web/static/css/custom.css +229 -0
  40. data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
  41. data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
  42. data/docs/web/static/fonts/StemText.woff +0 -0
  43. data/docs/web/static/fonts/StemTextBold.woff +0 -0
  44. data/docs/web/static/img/favicon/favicon.ico +0 -0
  45. data/docs/web/yarn.lock +1741 -0
  46. data/gemfiles/activerecord42.gemfile +1 -0
  47. data/gemfiles/jruby.gemfile +2 -0
  48. data/gemfiles/railsmaster.gemfile +1 -0
  49. data/lib/clowne.rb +3 -1
  50. data/lib/clowne/adapters/active_record.rb +3 -12
  51. data/lib/clowne/adapters/active_record/association.rb +1 -1
  52. data/lib/clowne/adapters/active_record/associations/base.rb +8 -48
  53. data/lib/clowne/adapters/active_record/associations/has_one.rb +8 -1
  54. data/lib/clowne/adapters/active_record/associations/noop.rb +4 -1
  55. data/lib/clowne/adapters/active_record/dsl.rb +33 -0
  56. data/lib/clowne/adapters/base.rb +13 -6
  57. data/lib/clowne/adapters/base/association.rb +69 -0
  58. data/lib/clowne/adapters/base/finalize.rb +1 -1
  59. data/lib/clowne/adapters/base/init_as.rb +21 -0
  60. data/lib/clowne/adapters/registry.rb +5 -11
  61. data/lib/clowne/adapters/sequel.rb +25 -0
  62. data/lib/clowne/adapters/sequel/association.rb +47 -0
  63. data/lib/clowne/adapters/sequel/associations.rb +26 -0
  64. data/lib/clowne/adapters/sequel/associations/base.rb +23 -0
  65. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +19 -0
  66. data/lib/clowne/adapters/sequel/associations/noop.rb +16 -0
  67. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +23 -0
  68. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +23 -0
  69. data/lib/clowne/adapters/sequel/copier.rb +23 -0
  70. data/lib/clowne/adapters/sequel/record_wrapper.rb +59 -0
  71. data/lib/clowne/cloner.rb +6 -4
  72. data/lib/clowne/declarations.rb +1 -0
  73. data/lib/clowne/declarations/exclude_association.rb +0 -5
  74. data/lib/clowne/declarations/include_association.rb +0 -2
  75. data/lib/clowne/declarations/init_as.rb +20 -0
  76. data/lib/clowne/declarations/trait.rb +2 -0
  77. data/lib/clowne/ext/orm_ext.rb +21 -0
  78. data/lib/clowne/ext/string_constantize.rb +2 -2
  79. data/lib/clowne/planner.rb +11 -4
  80. data/lib/clowne/version.rb +1 -1
  81. metadata +70 -4
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ class Association
7
+ class << self
8
+ def call(source, record, declaration, params:, **_options)
9
+ with_clonable(source, record, declaration) do
10
+ reflection = source.class.association_reflections[declaration.name.to_sym]
11
+
12
+ cloner_class = Associations.cloner_for(reflection)
13
+
14
+ cloner_class.new(reflection, source, declaration, params).call(record)
15
+
16
+ record
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def with_clonable(source, record, declaration)
23
+ if clonable_assoc?(source, declaration)
24
+ yield(record)
25
+ else
26
+ warn <<-WARN
27
+ Relation #{declaration.name} of #{source.class.name} does not configure for Sequel::Plugins::NestedAttributes
28
+ WARN
29
+ end
30
+
31
+ record
32
+ end
33
+
34
+ def clonable_assoc?(source, declaration)
35
+ source.class.plugins.include?(::Sequel::Plugins::NestedAttributes) &&
36
+ source.respond_to?(:"#{declaration.name.to_s}_attributes=")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Clowne::Adapters::Sequel.register_resolver(
45
+ :association,
46
+ Clowne::Adapters::Sequel::Association
47
+ )
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'clowne/adapters/sequel/associations/base'
4
+ require 'clowne/adapters/sequel/associations/noop'
5
+ require 'clowne/adapters/sequel/associations/one_to_one'
6
+ require 'clowne/adapters/sequel/associations/one_to_many'
7
+ require 'clowne/adapters/sequel/associations/many_to_many'
8
+
9
+ module Clowne
10
+ module Adapters # :nodoc: all
11
+ class Sequel
12
+ module Associations
13
+ SEQUEL_2_CLONER = {
14
+ one_to_one: OneToOne,
15
+ one_to_many: OneToMany,
16
+ many_to_many: ManyToMany
17
+ }.freeze
18
+
19
+ # Returns an association cloner class for reflection
20
+ def self.cloner_for(reflection)
21
+ SEQUEL_2_CLONER.fetch(reflection[:type], Noop)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'clowne/adapters/base/association'
4
+
5
+ module Clowne
6
+ module Adapters # :nodoc: all
7
+ class Sequel
8
+ module Associations
9
+ class Base < Base::Association
10
+ private
11
+
12
+ def clone_record(record)
13
+ Clowne::Adapters::Sequel::Copier.call(record)
14
+ end
15
+
16
+ def init_scope
17
+ @_init_scope ||= source.__send__([association_name, 'dataset'].join('_'))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ module Associations
7
+ class ManyToMany < Base
8
+ def call(record)
9
+ clones = with_scope.map { |child| clone_one(child) }
10
+
11
+ record.remember_assoc(:"#{association_name}_attributes", clones)
12
+
13
+ record
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ module Associations
7
+ class Noop < Base
8
+ def call(record)
9
+ warn("[Clowne] Reflection #{reflection.class.name} is not supported")
10
+ record
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ module Associations
7
+ class OneToMany < Base
8
+ def call(record)
9
+ clones =
10
+ with_scope.map do |child|
11
+ clone_one(child).tap do |child_clone|
12
+ child_clone[:"#{reflection[:key]}"] = nil
13
+ end
14
+ end
15
+ record.remember_assoc(:"#{association_name}_attributes", clones)
16
+
17
+ record
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ module Associations
7
+ class OneToOne < Base
8
+ def call(record)
9
+ child = association
10
+ return record unless child
11
+ warn '[Clowne] Has one association does not support scopes' unless scope.nil?
12
+
13
+ child_clone = clone_one(child)
14
+ child_clone[:"#{reflection[:key]}"] = nil
15
+ record.remember_assoc(:"#{association_name}_attributes", child_clone)
16
+
17
+ record
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ class Copier
7
+ class << self
8
+ def call(source)
9
+ nullify_attrs = [:create_timestamp_field, :update_timestamp_field].map do |timestamp|
10
+ source.class.instance_variable_get("@#{timestamp}")
11
+ end + [:id]
12
+
13
+ dup_hash = source.dup.to_hash.tap do |hash|
14
+ nullify_attrs.each { |field| hash.delete(field) }
15
+ end
16
+
17
+ source.class.new(dup_hash)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Sequel
6
+ class RecordWrapper
7
+ attr_reader :record, :association_store
8
+
9
+ def initialize(record)
10
+ @record = record
11
+ @association_store = {}
12
+ end
13
+
14
+ def remember_assoc(association, value)
15
+ @association_store[association] = value
16
+ end
17
+
18
+ def save
19
+ to_model.save
20
+ end
21
+
22
+ def to_model
23
+ association_store.each_with_object(record) do |(name, value), acc|
24
+ acc.send("#{name}=", association_to_model(value))
25
+ end
26
+ end
27
+
28
+ def to_hash
29
+ init_hash = record.to_hash
30
+ association_store.each_with_object(init_hash) do |(name, value), acc|
31
+ acc[name] = association_to_model(value)
32
+ end
33
+ end
34
+
35
+ def respond_to_missing?(method_name, include_private = false)
36
+ record.respond_to?(method_name) || super
37
+ end
38
+
39
+ def method_missing(method_name, *args, &block)
40
+ if record.respond_to?(method_name)
41
+ record.public_send(method_name, *args, &block)
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def association_to_model(assoc)
50
+ if assoc.is_a?(Array)
51
+ assoc.map(&:to_hash)
52
+ else
53
+ assoc.to_hash
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -38,8 +38,8 @@ module Clowne # :nodoc: all
38
38
  @traits[name].extend_with(block)
39
39
  end
40
40
 
41
- # rubocop: disable Metrics/AbcSize
42
- # rubocop: disable Metrics/MethodLength
41
+ # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
42
+ # rubocop: disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
43
43
  def call(object, **options)
44
44
  raise(UnprocessableSourceError, 'Nil is not cloneable object') if object.nil?
45
45
 
@@ -56,11 +56,13 @@ module Clowne # :nodoc: all
56
56
  plan_with_traits(traits)
57
57
  end
58
58
 
59
+ plan = Clowne::Planner.enhance(plan, Proc.new) if block_given?
60
+
59
61
  adapter.clone(object, plan, params: options)
60
62
  end
61
63
 
62
- # rubocop: enable Metrics/AbcSize
63
- # rubocop: enable Metrics/MethodLength
64
+ # rubocop: enable Metrics/AbcSize, Metrics/MethodLength
65
+ # rubocop: enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
64
66
 
65
67
  def default_plan
66
68
  return @default_plan if instance_variable_defined?(:@default_plan)
@@ -23,6 +23,7 @@ module Clowne
23
23
  end
24
24
  end
25
25
 
26
+ require 'clowne/declarations/init_as'
26
27
  require 'clowne/declarations/exclude_association'
27
28
  require 'clowne/declarations/finalize'
28
29
  require 'clowne/declarations/include_association'
@@ -11,11 +11,6 @@ module Clowne
11
11
 
12
12
  def compile(plan)
13
13
  plan.remove_from(:association, name)
14
-
15
- # update all_associations plan
16
- all_associations = plan.get(:all_associations)
17
- return if all_associations.nil?
18
- all_associations.except! name
19
14
  end
20
15
  end
21
16
  end
@@ -16,8 +16,6 @@ module Clowne
16
16
  end
17
17
 
18
18
  def compile(plan)
19
- # Clear `#include_all`
20
- plan.remove(:all_associations)
21
19
  plan.add_to(:association, name, self)
22
20
  end
23
21
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Declarations
5
+ class InitAs # :nodoc: all
6
+ attr_reader :block
7
+
8
+ def initialize
9
+ raise ArgumentError, 'Block is required for init_as' unless block_given?
10
+ @block = Proc.new
11
+ end
12
+
13
+ def compile(plan)
14
+ plan.set(:init_as, self)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Clowne::Declarations.add :init_as, Clowne::Declarations::InitAs
@@ -16,6 +16,8 @@ module Clowne
16
16
  @compiled = compile
17
17
  end
18
18
 
19
+ alias declarations compiled
20
+
19
21
  def dup
20
22
  self.class.new.tap do |duped|
21
23
  blocks.each { |b| duped.extend_with(b) }
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'clowne/ext/string_constantize'
4
+
5
+ module Clowne
6
+ module Ext
7
+ # Adds #cloner_class method to ORM base model
8
+ module ORMExt
9
+ using Clowne::Ext::StringConstantize
10
+
11
+ def cloner_class
12
+ return @_clowne_cloner if instance_variable_defined?(:@_clowne_cloner)
13
+
14
+ cloner = "#{name}Cloner".constantize
15
+ return @_clowne_cloner = cloner if cloner && cloner <= Clowne::Cloner
16
+
17
+ @_clowne_cloner = superclass.cloner_class if superclass.respond_to?(:cloner_class)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -8,13 +8,13 @@ module Clowne
8
8
  def constantize
9
9
  names = split('::')
10
10
 
11
- Object.const_get(self) if names.empty?
11
+ return nil if names.empty?
12
12
 
13
13
  # Remove the first blank element in case of '::ClassName' notation.
14
14
  names.shift if names.size > 1 && names.first.empty?
15
15
 
16
16
  names.inject(Object) do |constant, name|
17
- constant.const_get(name)
17
+ constant.const_get(name) if constant.const_defined?(name)
18
18
  end
19
19
  end
20
20
  end
@@ -5,10 +5,7 @@ require 'clowne/plan'
5
5
  module Clowne
6
6
  class Planner # :nodoc: all
7
7
  class << self
8
- # Params:
9
- # +cloner+:: Cloner object
10
- # +init_plan+:: Init plan
11
- # +traits+:: List of traits if any
8
+ # Compile plan for cloner with traits
12
9
  def compile(cloner, traits: nil)
13
10
  declarations = cloner.declarations.dup
14
11
 
@@ -19,6 +16,16 @@ module Clowne
19
16
  end
20
17
  end
21
18
 
19
+ # Extend previously compiled plan with an arbitrary block
20
+ # NOTE: It doesn't modify the plan itself but return a copy
21
+ def enhance(plan, block)
22
+ trait = Clowne::Declarations::Trait.new.tap { |t| t.extend_with(block) }
23
+
24
+ trait.compiled.each_with_object(plan.dup) do |declaration, new_plan|
25
+ declaration.compile(new_plan)
26
+ end
27
+ end
28
+
22
29
  private
23
30
 
24
31
  def compile_traits(cloner, traits)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clowne
4
- VERSION = '0.1.0.pre1'
4
+ VERSION = '0.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: 0.1.0.pre1
4
+ version: 0.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: 2018-01-05 00:00:00.000000000 Z
12
+ date: 2018-02-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -81,6 +81,20 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0.51'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop-md
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0.2'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0.2'
84
98
  description: A flexible gem for cloning your models.
85
99
  email:
86
100
  - palkan@evilmartians.com
@@ -89,6 +103,8 @@ executables: []
89
103
  extensions: []
90
104
  extra_rdoc_files: []
91
105
  files:
106
+ - ".codeclimate.yml"
107
+ - ".gitattributes"
92
108
  - ".gitignore"
93
109
  - ".rspec"
94
110
  - ".rubocop.yml"
@@ -102,6 +118,41 @@ files:
102
118
  - bin/console
103
119
  - bin/setup
104
120
  - clowne.gemspec
121
+ - docs/.rubocop.yml
122
+ - docs/active_record.md
123
+ - docs/alternatives.md
124
+ - docs/architecture.md
125
+ - docs/basic_example.md
126
+ - docs/configuration.md
127
+ - docs/customization.md
128
+ - docs/exclude_association.md
129
+ - docs/execution_order.md
130
+ - docs/finalize.md
131
+ - docs/implicit_cloner.md
132
+ - docs/include_association.md
133
+ - docs/init_as.md
134
+ - docs/inline_configuration.md
135
+ - docs/installation.md
136
+ - docs/nullify.md
137
+ - docs/sequel.md
138
+ - docs/supported_adapters.md
139
+ - docs/traits.md
140
+ - docs/web/.gitignore
141
+ - docs/web/core/Footer.js
142
+ - docs/web/i18n/en.json
143
+ - docs/web/package.json
144
+ - docs/web/pages/en/help.js
145
+ - docs/web/pages/en/index.js
146
+ - docs/web/pages/en/users.js
147
+ - docs/web/sidebars.json
148
+ - docs/web/siteConfig.js
149
+ - docs/web/static/css/custom.css
150
+ - docs/web/static/fonts/FiraCode-Medium.woff
151
+ - docs/web/static/fonts/FiraCode-Regular.woff
152
+ - docs/web/static/fonts/StemText.woff
153
+ - docs/web/static/fonts/StemTextBold.woff
154
+ - docs/web/static/img/favicon/favicon.ico
155
+ - docs/web/yarn.lock
105
156
  - gemfiles/activerecord42.gemfile
106
157
  - gemfiles/jruby.gemfile
107
158
  - gemfiles/railsmaster.gemfile
@@ -114,18 +165,33 @@ files:
114
165
  - lib/clowne/adapters/active_record/associations/has_many.rb
115
166
  - lib/clowne/adapters/active_record/associations/has_one.rb
116
167
  - lib/clowne/adapters/active_record/associations/noop.rb
168
+ - lib/clowne/adapters/active_record/dsl.rb
117
169
  - lib/clowne/adapters/base.rb
170
+ - lib/clowne/adapters/base/association.rb
118
171
  - lib/clowne/adapters/base/finalize.rb
172
+ - lib/clowne/adapters/base/init_as.rb
119
173
  - lib/clowne/adapters/base/nullify.rb
120
174
  - lib/clowne/adapters/registry.rb
175
+ - lib/clowne/adapters/sequel.rb
176
+ - lib/clowne/adapters/sequel/association.rb
177
+ - lib/clowne/adapters/sequel/associations.rb
178
+ - lib/clowne/adapters/sequel/associations/base.rb
179
+ - lib/clowne/adapters/sequel/associations/many_to_many.rb
180
+ - lib/clowne/adapters/sequel/associations/noop.rb
181
+ - lib/clowne/adapters/sequel/associations/one_to_many.rb
182
+ - lib/clowne/adapters/sequel/associations/one_to_one.rb
183
+ - lib/clowne/adapters/sequel/copier.rb
184
+ - lib/clowne/adapters/sequel/record_wrapper.rb
121
185
  - lib/clowne/cloner.rb
122
186
  - lib/clowne/declarations.rb
123
187
  - lib/clowne/declarations/exclude_association.rb
124
188
  - lib/clowne/declarations/finalize.rb
125
189
  - lib/clowne/declarations/include_association.rb
190
+ - lib/clowne/declarations/init_as.rb
126
191
  - lib/clowne/declarations/nullify.rb
127
192
  - lib/clowne/declarations/trait.rb
128
193
  - lib/clowne/dsl.rb
194
+ - lib/clowne/ext/orm_ext.rb
129
195
  - lib/clowne/ext/string_constantize.rb
130
196
  - lib/clowne/plan.rb
131
197
  - lib/clowne/planner.rb
@@ -145,9 +211,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
211
  version: '0'
146
212
  required_rubygems_version: !ruby/object:Gem::Requirement
147
213
  requirements:
148
- - - ">"
214
+ - - ">="
149
215
  - !ruby/object:Gem::Version
150
- version: 1.3.1
216
+ version: '0'
151
217
  requirements: []
152
218
  rubyforge_project:
153
219
  rubygems_version: 2.6.13