clowne 0.1.0.pre1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +7 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +16 -33
  6. data/.travis.yml +14 -10
  7. data/CHANGELOG.md +39 -2
  8. data/Gemfile +11 -6
  9. data/README.md +48 -384
  10. data/Rakefile +3 -3
  11. data/clowne.gemspec +16 -8
  12. data/docs/.nojekyll +0 -0
  13. data/docs/.rubocop.yml +18 -0
  14. data/docs/CNAME +1 -0
  15. data/docs/README.md +131 -0
  16. data/docs/_sidebar.md +25 -0
  17. data/docs/active_record.md +33 -0
  18. data/docs/after_clone.md +53 -0
  19. data/docs/after_persist.md +77 -0
  20. data/docs/architecture.md +138 -0
  21. data/docs/assets/docsify.min.js +1 -0
  22. data/docs/assets/prism-ruby.min.js +1 -0
  23. data/docs/assets/styles.css +348 -0
  24. data/docs/assets/vue.css +1 -0
  25. data/docs/clone_mapper.md +59 -0
  26. data/docs/customization.md +63 -0
  27. data/docs/exclude_association.md +61 -0
  28. data/docs/finalize.md +31 -0
  29. data/docs/from_v02_to_v1.md +83 -0
  30. data/docs/getting_started.md +171 -0
  31. data/docs/implicit_cloner.md +33 -0
  32. data/docs/include_association.md +133 -0
  33. data/docs/index.html +29 -0
  34. data/docs/init_as.md +40 -0
  35. data/docs/inline_configuration.md +37 -0
  36. data/docs/nullify.md +33 -0
  37. data/docs/operation.md +55 -0
  38. data/docs/parameters.md +112 -0
  39. data/docs/sequel.md +50 -0
  40. data/docs/supported_adapters.md +10 -0
  41. data/docs/testing.md +194 -0
  42. data/docs/traits.md +25 -0
  43. data/gemfiles/activerecord42.gemfile +7 -4
  44. data/gemfiles/jruby.gemfile +8 -4
  45. data/gemfiles/railsmaster.gemfile +8 -5
  46. data/lib/clowne.rb +12 -7
  47. data/lib/clowne/adapters/active_record.rb +6 -16
  48. data/lib/clowne/adapters/active_record/associations.rb +8 -6
  49. data/lib/clowne/adapters/active_record/associations/base.rb +5 -49
  50. data/lib/clowne/adapters/active_record/associations/belongs_to.rb +29 -0
  51. data/lib/clowne/adapters/active_record/associations/has_one.rb +9 -1
  52. data/lib/clowne/adapters/active_record/associations/noop.rb +4 -1
  53. data/lib/clowne/adapters/active_record/dsl.rb +33 -0
  54. data/lib/clowne/adapters/active_record/resolvers/association.rb +38 -0
  55. data/lib/clowne/adapters/base.rb +53 -41
  56. data/lib/clowne/adapters/base/association.rb +78 -0
  57. data/lib/clowne/adapters/registry.rb +54 -11
  58. data/lib/clowne/adapters/sequel.rb +29 -0
  59. data/lib/clowne/adapters/sequel/associations.rb +26 -0
  60. data/lib/clowne/adapters/sequel/associations/base.rb +27 -0
  61. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +23 -0
  62. data/lib/clowne/adapters/sequel/associations/noop.rb +16 -0
  63. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +28 -0
  64. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +28 -0
  65. data/lib/clowne/adapters/sequel/copier.rb +23 -0
  66. data/lib/clowne/adapters/sequel/operation.rb +35 -0
  67. data/lib/clowne/adapters/sequel/record_wrapper.rb +43 -0
  68. data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +22 -0
  69. data/lib/clowne/adapters/sequel/resolvers/association.rb +51 -0
  70. data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +15 -0
  71. data/lib/clowne/cloner.rb +50 -20
  72. data/lib/clowne/declarations.rb +15 -11
  73. data/lib/clowne/declarations/after_clone.rb +21 -0
  74. data/lib/clowne/declarations/after_persist.rb +21 -0
  75. data/lib/clowne/declarations/base.rb +13 -0
  76. data/lib/clowne/declarations/exclude_association.rb +1 -6
  77. data/lib/clowne/declarations/finalize.rb +3 -2
  78. data/lib/clowne/declarations/include_association.rb +16 -4
  79. data/lib/clowne/declarations/init_as.rb +21 -0
  80. data/lib/clowne/declarations/nullify.rb +3 -2
  81. data/lib/clowne/declarations/trait.rb +3 -0
  82. data/lib/clowne/dsl.rb +9 -0
  83. data/lib/clowne/ext/lambda_as_proc.rb +17 -0
  84. data/lib/clowne/ext/orm_ext.rb +21 -0
  85. data/lib/clowne/ext/record_key.rb +12 -0
  86. data/lib/clowne/ext/string_constantize.rb +10 -4
  87. data/lib/clowne/ext/yield_self_then.rb +25 -0
  88. data/lib/clowne/planner.rb +27 -7
  89. data/lib/clowne/resolvers/after_clone.rb +17 -0
  90. data/lib/clowne/resolvers/after_persist.rb +18 -0
  91. data/lib/clowne/resolvers/finalize.rb +12 -0
  92. data/lib/clowne/resolvers/init_as.rb +13 -0
  93. data/lib/clowne/resolvers/nullify.rb +15 -0
  94. data/lib/clowne/rspec.rb +5 -0
  95. data/lib/clowne/rspec/clone_association.rb +99 -0
  96. data/lib/clowne/rspec/clone_associations.rb +26 -0
  97. data/lib/clowne/rspec/helpers.rb +35 -0
  98. data/lib/clowne/utils/clone_mapper.rb +26 -0
  99. data/lib/clowne/utils/operation.rb +95 -0
  100. data/lib/clowne/utils/options.rb +39 -0
  101. data/lib/clowne/utils/params.rb +64 -0
  102. data/lib/clowne/utils/plan.rb +90 -0
  103. data/lib/clowne/version.rb +1 -1
  104. metadata +140 -20
  105. data/lib/clowne/adapters/active_record/association.rb +0 -34
  106. data/lib/clowne/adapters/base/finalize.rb +0 -19
  107. data/lib/clowne/adapters/base/nullify.rb +0 -19
  108. data/lib/clowne/plan.rb +0 -81
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class Base
6
+ class Association
7
+ # Params:
8
+ # +reflection+:: Association eflection object
9
+ # +source+:: Instance of cloned object (ex: User.new(posts: posts))
10
+ # +declaration+:: = Relation description
11
+ # (ex: Clowne::Declarations::IncludeAssociation.new(:posts))
12
+ # +adapter+:: Clowne adapter
13
+ # +params+:: = Instance of Hash
14
+ def initialize(reflection, source, declaration, adapter, params)
15
+ @source = source
16
+ @declaration = declaration
17
+ @adapter = adapter
18
+ @params = params
19
+ @association_name = declaration.name.to_s
20
+ @reflection = reflection
21
+ end
22
+
23
+ def call(_record)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def association
28
+ @_association ||= source.__send__(association_name)
29
+ end
30
+
31
+ def clone_one(child)
32
+ cloner = cloner_for(child)
33
+ cloner ? cloner.call(child, cloner_options) : dup_record(child)
34
+ end
35
+
36
+ def with_scope
37
+ scope = declaration.scope
38
+ if scope.is_a?(Symbol)
39
+ init_scope.__send__(scope)
40
+ elsif scope.is_a?(Proc)
41
+ init_scope.instance_exec(params, &scope) || init_scope
42
+ else
43
+ init_scope
44
+ end.to_a
45
+ end
46
+
47
+ private
48
+
49
+ def dup_record(record)
50
+ adapter.class.dup_record(record)
51
+ end
52
+
53
+ def init_scope
54
+ raise NotImplementedError
55
+ end
56
+
57
+ def cloner_for(child)
58
+ return declaration.clone_with if declaration.clone_with
59
+
60
+ return child.class.cloner_class if child.class.respond_to?(:cloner_class)
61
+ end
62
+
63
+ def cloner_options
64
+ return @_cloner_options if defined?(@_cloner_options)
65
+
66
+ @_cloner_options = declaration.params_proxy.permit(
67
+ params: params, parent: source
68
+ ).tap do |options|
69
+ options[:adapter] = adapter
70
+ options.merge!(traits: declaration.traits) if declaration.traits
71
+ end
72
+ end
73
+
74
+ attr_reader :source, :declaration, :adapter, :params, :association_name, :reflection
75
+ end
76
+ end
77
+ end
78
+ end
@@ -3,6 +3,55 @@
3
3
  module Clowne
4
4
  module Adapters
5
5
  class Registry # :nodoc: all
6
+ module Container
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+
10
+ base.class_eval do
11
+ self.registry = Registry.new
12
+
13
+ def registry
14
+ self.class.registry
15
+ end
16
+
17
+ def resolver_for(type)
18
+ self.class.resolver_for(type)
19
+ end
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ attr_reader :registry
25
+
26
+ def inherited(subclass)
27
+ # Duplicate registry
28
+ subclass.registry = registry.dup
29
+ end
30
+
31
+ def resolver_for(type)
32
+ registry.mapping[type] || raise("Uknown resolver #{type} for #{self}")
33
+ end
34
+
35
+ def register_resolver(type, resolver, after: nil, before: nil, prepend: nil)
36
+ registry.mapping[type] = resolver
37
+
38
+ if prepend
39
+ registry.unshift type
40
+ elsif after
41
+ registry.insert_after after, type
42
+ elsif before
43
+ registry.insert_before before, type
44
+ else
45
+ registry.append type
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ attr_writer :registry
52
+ end
53
+ end
54
+
6
55
  attr_reader :actions, :mapping
7
56
 
8
57
  def initialize
@@ -11,7 +60,7 @@ module Clowne
11
60
  end
12
61
 
13
62
  def insert_after(after, action)
14
- validate_uniq!(action)
63
+ actions.delete(action)
15
64
 
16
65
  after_index = actions.find_index(after)
17
66
 
@@ -21,7 +70,7 @@ module Clowne
21
70
  end
22
71
 
23
72
  def insert_before(before, action)
24
- validate_uniq!(action)
73
+ actions.delete(action)
25
74
 
26
75
  before_index = actions.find_index(before)
27
76
 
@@ -31,12 +80,12 @@ module Clowne
31
80
  end
32
81
 
33
82
  def append(action)
34
- validate_uniq!(action)
83
+ actions.delete(action)
35
84
  actions.push action
36
85
  end
37
86
 
38
- def prepend(action)
39
- validate_uniq!(action)
87
+ def unshift(action)
88
+ actions.delete(action)
40
89
  actions.unshift action
41
90
  end
42
91
 
@@ -50,12 +99,6 @@ module Clowne
50
99
  protected
51
100
 
52
101
  attr_writer :mapping
53
-
54
- private
55
-
56
- def validate_uniq!(action)
57
- raise "Plan action already registered: #{action}" if actions.include?(action)
58
- end
59
102
  end
60
103
  end
61
104
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clowne/ext/orm_ext"
4
+
5
+ module Clowne
6
+ module Adapters
7
+ # Cloning adapter for Sequel
8
+ class Sequel < Base
9
+ class << self
10
+ def dup_record(record)
11
+ Clowne::Adapters::Sequel::Copier.call(record)
12
+ end
13
+
14
+ def operation_class
15
+ Clowne::Adapters::Sequel::Operation
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ ::Sequel::Model.extend Clowne::Ext::ORMExt
23
+
24
+ require "clowne/adapters/sequel/operation"
25
+ require "clowne/adapters/sequel/associations"
26
+ require "clowne/adapters/sequel/copier"
27
+ require "clowne/adapters/sequel/record_wrapper"
28
+ require "clowne/adapters/sequel/resolvers/association"
29
+ require "clowne/adapters/sequel/resolvers/after_persist"
@@ -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,27 @@
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 init_scope
13
+ @_init_scope ||= source.__send__([association_name, "dataset"].join("_"))
14
+ end
15
+
16
+ def record_wrapper(record)
17
+ operation.record_wrapper(record)
18
+ end
19
+
20
+ def operation
21
+ @_operation ||= adapter.class.operation_class.current
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ 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 ManyToMany < Base
8
+ def call(record)
9
+ clones = with_scope
10
+ .lazy
11
+ .map(&method(:clone_one))
12
+ .map(&method(:record_wrapper))
13
+ .to_a
14
+
15
+ record_wrapper(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,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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clowne/ext/yield_self_then"
4
+
5
+ using Clowne::Ext::YieldSelfThen
6
+
7
+ module Clowne
8
+ module Adapters # :nodoc: all
9
+ class Sequel
10
+ module Associations
11
+ class OneToMany < Base
12
+ def call(record)
13
+ clones =
14
+ with_scope.map do |child|
15
+ clone_one(child).tap do |child_clone|
16
+ child_clone[:"#{reflection[:key]}"] = nil
17
+ end.then(&method(:record_wrapper))
18
+ end
19
+
20
+ record_wrapper(record).remember_assoc(:"#{association_name}_attributes", clones)
21
+
22
+ record
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
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
+
12
+ warn "[Clowne] Has one association does not support scope" unless declaration.scope.nil?
13
+
14
+ child_clone = clone_one(child)
15
+ child_clone[:"#{reflection[:key]}"] = nil
16
+
17
+ record_wrapper(record).remember_assoc(
18
+ :"#{association_name}_attributes",
19
+ record_wrapper(child_clone)
20
+ )
21
+
22
+ record
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ 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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clowne/ext/record_key"
4
+
5
+ module Clowne
6
+ module Adapters
7
+ class Sequel # :nodoc: all
8
+ class Operation < Clowne::Utils::Operation
9
+ include Clowne::Ext::RecordKey
10
+ def initialize(mapper)
11
+ super
12
+ @records = {}
13
+ end
14
+
15
+ def record_wrapper(record)
16
+ @records[key(record)] ||= RecordWrapper.new(record)
17
+ end
18
+
19
+ def hash
20
+ @records[key(@clone)]
21
+ end
22
+
23
+ def to_record
24
+ return @_record if defined?(@_record)
25
+
26
+ record_wrapper(@clone)
27
+
28
+ @_record = @records[key(@clone)].to_model.tap do
29
+ run_after_clone
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
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 to_model
19
+ association_store.each_with_object(record) do |(name, value), acc|
20
+ acc.send("#{name}=", association_to_model(value))
21
+ end
22
+ end
23
+
24
+ def to_hash
25
+ init_hash = record.to_hash
26
+ association_store.each_with_object(init_hash) do |(name, value), acc|
27
+ acc[name] = association_to_model(value)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def association_to_model(assoc)
34
+ if assoc.is_a?(Array)
35
+ assoc.map(&:to_hash)
36
+ else
37
+ assoc.to_hash
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end