clowne 0.1.0.pre1 → 0.1.0

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