clowne 0.2.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.travis.yml +6 -3
- data/CHANGELOG.md +14 -0
- data/Gemfile +1 -1
- data/README.md +30 -9
- data/clowne.gemspec +4 -3
- data/docs/active_record.md +2 -2
- data/docs/after_persist.md +80 -0
- data/docs/basic_example.md +22 -5
- data/docs/clone_mapper.md +62 -0
- data/docs/customization.md +2 -1
- data/docs/exclude_association.md +5 -4
- data/docs/finalize.md +2 -2
- data/docs/from_v02_to_v1.md +91 -0
- data/docs/implicit_cloner.md +1 -1
- data/docs/include_association.md +4 -4
- data/docs/init_as.md +10 -2
- data/docs/inline_configuration.md +4 -2
- data/docs/installation.md +31 -1
- data/docs/nullify.md +2 -2
- data/docs/operation.md +58 -0
- data/docs/overview.md +24 -0
- data/docs/parameters.md +5 -4
- data/docs/sequel.md +15 -18
- data/docs/supported_adapters.md +2 -2
- data/docs/testing.md +7 -5
- data/docs/web/README.md +6 -0
- data/docs/web/core/Footer.js +5 -9
- data/docs/web/i18n/en.json +8 -4
- data/docs/web/pages/en/index.js +1 -1
- data/docs/web/sidebars.json +10 -4
- data/docs/web/siteConfig.js +6 -4
- data/docs/web/static/css/custom.css +16 -10
- data/gemfiles/activerecord42.gemfile +3 -1
- data/gemfiles/jruby.gemfile +2 -0
- data/gemfiles/railsmaster.gemfile +2 -0
- data/lib/clowne.rb +3 -0
- data/lib/clowne/adapters/active_record.rb +2 -3
- data/lib/clowne/adapters/active_record/associations/base.rb +0 -4
- data/lib/clowne/adapters/active_record/associations/has_one.rb +2 -1
- data/lib/clowne/adapters/active_record/resolvers/association.rb +38 -0
- data/lib/clowne/adapters/base.rb +42 -43
- data/lib/clowne/adapters/base/association.rb +24 -15
- data/lib/clowne/adapters/registry.rb +49 -0
- data/lib/clowne/adapters/sequel.rb +10 -6
- data/lib/clowne/adapters/sequel/associations/base.rb +8 -4
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +6 -2
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +7 -2
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +7 -2
- data/lib/clowne/adapters/sequel/operation.rb +32 -0
- data/lib/clowne/adapters/sequel/record_wrapper.rb +0 -16
- data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +22 -0
- data/lib/clowne/adapters/sequel/resolvers/association.rb +51 -0
- data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +15 -0
- data/lib/clowne/cloner.rb +27 -20
- data/lib/clowne/declarations.rb +2 -1
- data/lib/clowne/declarations/after_persist.rb +21 -0
- data/lib/clowne/declarations/finalize.rb +1 -0
- data/lib/clowne/declarations/include_association.rb +2 -1
- data/lib/clowne/declarations/init_as.rb +1 -0
- data/lib/clowne/declarations/nullify.rb +1 -0
- data/lib/clowne/declarations/trait.rb +1 -0
- data/lib/clowne/dsl.rb +9 -0
- data/lib/clowne/ext/lambda_as_proc.rb +1 -0
- data/lib/clowne/ext/record_key.rb +12 -0
- data/lib/clowne/ext/yield_self_then.rb +25 -0
- data/lib/clowne/planner.rb +6 -3
- data/lib/clowne/resolvers/after_persist.rb +18 -0
- data/lib/clowne/resolvers/finalize.rb +12 -0
- data/lib/clowne/resolvers/init_as.rb +13 -0
- data/lib/clowne/resolvers/nullify.rb +15 -0
- data/lib/clowne/rspec/helpers.rb +1 -0
- data/lib/clowne/utils/clone_mapper.rb +26 -0
- data/lib/clowne/utils/operation.rb +83 -0
- data/lib/clowne/utils/options.rb +39 -0
- data/lib/clowne/utils/params.rb +64 -0
- data/lib/clowne/utils/plan.rb +90 -0
- data/lib/clowne/version.rb +1 -1
- metadata +44 -18
- data/docs/configuration.md +0 -29
- data/docs/execution_order.md +0 -14
- data/docs/web/static/fonts/StemText.woff +0 -0
- data/docs/web/static/fonts/StemTextBold.woff +0 -0
- data/lib/clowne/adapters/active_record/association.rb +0 -34
- data/lib/clowne/adapters/base/finalize.rb +0 -19
- data/lib/clowne/adapters/base/init_as.rb +0 -21
- data/lib/clowne/adapters/base/nullify.rb +0 -19
- data/lib/clowne/adapters/sequel/association.rb +0 -47
- data/lib/clowne/params.rb +0 -62
- data/lib/clowne/plan.rb +0 -83
@@ -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
|
@@ -6,12 +6,14 @@ module Clowne
|
|
6
6
|
module Adapters
|
7
7
|
# Cloning adapter for Sequel
|
8
8
|
class Sequel < Base
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
class << self
|
10
|
+
def dup_record(record)
|
11
|
+
Clowne::Adapters::Sequel::Copier.call(record)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
def operation_class
|
15
|
+
Clowne::Adapters::Sequel::Operation
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -19,7 +21,9 @@ end
|
|
19
21
|
|
20
22
|
::Sequel::Model.extend Clowne::Ext::ORMExt
|
21
23
|
|
24
|
+
require 'clowne/adapters/sequel/operation'
|
22
25
|
require 'clowne/adapters/sequel/associations'
|
23
|
-
require 'clowne/adapters/sequel/association'
|
24
26
|
require 'clowne/adapters/sequel/copier'
|
25
27
|
require 'clowne/adapters/sequel/record_wrapper'
|
28
|
+
require 'clowne/adapters/sequel/resolvers/association'
|
29
|
+
require 'clowne/adapters/sequel/resolvers/after_persist'
|
@@ -9,13 +9,17 @@ module Clowne
|
|
9
9
|
class Base < Base::Association
|
10
10
|
private
|
11
11
|
|
12
|
-
def clone_record(record)
|
13
|
-
Clowne::Adapters::Sequel::Copier.call(record)
|
14
|
-
end
|
15
|
-
|
16
12
|
def init_scope
|
17
13
|
@_init_scope ||= source.__send__([association_name, 'dataset'].join('_'))
|
18
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
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
@@ -6,9 +6,13 @@ module Clowne
|
|
6
6
|
module Associations
|
7
7
|
class ManyToMany < Base
|
8
8
|
def call(record)
|
9
|
-
clones = with_scope
|
9
|
+
clones = with_scope
|
10
|
+
.lazy
|
11
|
+
.map(&method(:clone_one))
|
12
|
+
.map(&method(:record_wrapper))
|
13
|
+
.to_a
|
10
14
|
|
11
|
-
record.remember_assoc(:"#{association_name}_attributes", clones)
|
15
|
+
record_wrapper(record).remember_assoc(:"#{association_name}_attributes", clones)
|
12
16
|
|
13
17
|
record
|
14
18
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'clowne/ext/yield_self_then'
|
4
|
+
|
5
|
+
using Clowne::Ext::YieldSelfThen
|
6
|
+
|
3
7
|
module Clowne
|
4
8
|
module Adapters # :nodoc: all
|
5
9
|
class Sequel
|
@@ -10,9 +14,10 @@ module Clowne
|
|
10
14
|
with_scope.map do |child|
|
11
15
|
clone_one(child).tap do |child_clone|
|
12
16
|
child_clone[:"#{reflection[:key]}"] = nil
|
13
|
-
end
|
17
|
+
end.then(&method(:record_wrapper))
|
14
18
|
end
|
15
|
-
|
19
|
+
|
20
|
+
record_wrapper(record).remember_assoc(:"#{association_name}_attributes", clones)
|
16
21
|
|
17
22
|
record
|
18
23
|
end
|
@@ -8,11 +8,16 @@ module Clowne
|
|
8
8
|
def call(record)
|
9
9
|
child = association
|
10
10
|
return record unless child
|
11
|
-
|
11
|
+
|
12
|
+
warn '[Clowne] Has one association does not support scope' unless declaration.scope.nil?
|
12
13
|
|
13
14
|
child_clone = clone_one(child)
|
14
15
|
child_clone[:"#{reflection[:key]}"] = nil
|
15
|
-
|
16
|
+
|
17
|
+
record_wrapper(record).remember_assoc(
|
18
|
+
:"#{association_name}_attributes",
|
19
|
+
record_wrapper(child_clone)
|
20
|
+
)
|
16
21
|
|
17
22
|
record
|
18
23
|
end
|
@@ -0,0 +1,32 @@
|
|
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
|
+
|
11
|
+
def initialize(mapper)
|
12
|
+
super
|
13
|
+
@records = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def record_wrapper(record)
|
17
|
+
@records[key(record)] ||= RecordWrapper.new(record)
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
@records[key(@clone)]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_record
|
25
|
+
return @_record if defined?(@_record)
|
26
|
+
|
27
|
+
@_record = @records[key(@clone)].to_model
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -15,10 +15,6 @@ module Clowne
|
|
15
15
|
@association_store[association] = value
|
16
16
|
end
|
17
17
|
|
18
|
-
def save
|
19
|
-
to_model.save
|
20
|
-
end
|
21
|
-
|
22
18
|
def to_model
|
23
19
|
association_store.each_with_object(record) do |(name, value), acc|
|
24
20
|
acc.send("#{name}=", association_to_model(value))
|
@@ -32,18 +28,6 @@ module Clowne
|
|
32
28
|
end
|
33
29
|
end
|
34
30
|
|
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
31
|
private
|
48
32
|
|
49
33
|
def association_to_model(assoc)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clowne/adapters/sequel/specifications/after_persist_does_not_support'
|
4
|
+
|
5
|
+
module Clowne
|
6
|
+
module Adapters # :nodoc: all
|
7
|
+
class Sequel
|
8
|
+
module Resolvers
|
9
|
+
class AfterPersist
|
10
|
+
def self.call(_source, _record, _declaration, **)
|
11
|
+
raise Specifications::AfterPersistDoesNotSupportException
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Clowne::Adapters::Sequel.register_resolver(
|
20
|
+
:after_persist,
|
21
|
+
Clowne::Adapters::Sequel::Resolvers::AfterPersist
|
22
|
+
)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Adapters # :nodoc: all
|
5
|
+
class Sequel
|
6
|
+
module Resolvers
|
7
|
+
class Association
|
8
|
+
class << self
|
9
|
+
# rubocop: disable Metrics/ParameterLists
|
10
|
+
def call(source, record, declaration, adapter:, params:, **_options)
|
11
|
+
with_clonable(source, record, declaration) do
|
12
|
+
reflection = source.class.association_reflections[declaration.name.to_sym]
|
13
|
+
|
14
|
+
cloner_class = Associations.cloner_for(reflection)
|
15
|
+
|
16
|
+
cloner_class.new(reflection, source, declaration, adapter, params).call(record)
|
17
|
+
|
18
|
+
record
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# rubocop: enable Metrics/ParameterLists
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def with_clonable(source, record, declaration)
|
26
|
+
if clonable_assoc?(source, declaration)
|
27
|
+
yield(record)
|
28
|
+
else
|
29
|
+
warn <<-WARN
|
30
|
+
Relation #{declaration.name} of #{source.class.name} does not configure for Sequel::Plugins::NestedAttributes
|
31
|
+
WARN
|
32
|
+
end
|
33
|
+
|
34
|
+
record
|
35
|
+
end
|
36
|
+
|
37
|
+
def clonable_assoc?(source, declaration)
|
38
|
+
source.class.plugins.include?(::Sequel::Plugins::NestedAttributes) &&
|
39
|
+
source.respond_to?(:"#{declaration.name.to_s}_attributes=")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Clowne::Adapters::Sequel.register_resolver(
|
49
|
+
:association,
|
50
|
+
Clowne::Adapters::Sequel::Resolvers::Association
|
51
|
+
)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Adapters
|
5
|
+
class Sequel
|
6
|
+
module Specifications # :nodoc: all
|
7
|
+
class AfterPersistDoesNotSupportException < StandardError
|
8
|
+
def message
|
9
|
+
'Sequel adapter does not support after_persist callback'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/clowne/cloner.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'clowne/planner'
|
4
4
|
require 'clowne/dsl'
|
5
|
-
require 'clowne/
|
5
|
+
require 'clowne/utils/options'
|
6
|
+
require 'clowne/utils/params'
|
7
|
+
require 'clowne/utils/operation'
|
6
8
|
|
7
9
|
module Clowne # :nodoc: all
|
8
10
|
class UnprocessableSourceError < StandardError; end
|
@@ -25,11 +27,13 @@ module Clowne # :nodoc: all
|
|
25
27
|
|
26
28
|
def declarations
|
27
29
|
return @declarations if instance_variable_defined?(:@declarations)
|
30
|
+
|
28
31
|
@declarations = []
|
29
32
|
end
|
30
33
|
|
31
34
|
def traits
|
32
35
|
return @traits if instance_variable_defined?(:@traits)
|
36
|
+
|
33
37
|
@traits = {}
|
34
38
|
end
|
35
39
|
|
@@ -40,50 +44,46 @@ module Clowne # :nodoc: all
|
|
40
44
|
end
|
41
45
|
|
42
46
|
# rubocop: disable Metrics/AbcSize, Metrics/MethodLength
|
43
|
-
# rubocop: disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
44
47
|
def call(object, **options)
|
45
48
|
raise(UnprocessableSourceError, 'Nil is not cloneable object') if object.nil?
|
46
49
|
|
47
|
-
|
48
|
-
|
49
|
-
traits = options.delete(:traits)
|
50
|
-
|
51
|
-
only = options.delete(:clowne_only_actions)
|
50
|
+
options = Clowne::Utils::Options.new(options)
|
51
|
+
current_adapter = current_adapter(options.adapter)
|
52
52
|
|
53
|
-
|
53
|
+
raise(ConfigurationError, 'Adapter is not defined') if current_adapter.nil?
|
54
54
|
|
55
55
|
plan =
|
56
|
-
if
|
57
|
-
default_plan
|
56
|
+
if options.traits.empty?
|
57
|
+
default_plan(current_adapter: current_adapter)
|
58
58
|
else
|
59
|
-
plan_with_traits(traits)
|
59
|
+
plan_with_traits(options.traits, current_adapter: current_adapter)
|
60
60
|
end
|
61
61
|
|
62
62
|
plan = Clowne::Planner.enhance(plan, Proc.new) if block_given?
|
63
63
|
|
64
|
-
plan = Clowne::Planner.filter_declarations(plan, only)
|
64
|
+
plan = Clowne::Planner.filter_declarations(plan, options.only)
|
65
65
|
|
66
|
-
|
66
|
+
call_operation(current_adapter, object, plan, options)
|
67
67
|
end
|
68
|
+
# rubocop: enable Metrics/AbcSize, Metrics/MethodLength
|
68
69
|
|
69
70
|
def partial_apply(only, *args, **hargs)
|
70
71
|
call(*args, **hargs, clowne_only_actions: prepare_only(only))
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
-
# rubocop: enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
75
|
-
|
76
|
-
def default_plan
|
74
|
+
def default_plan(current_adapter: adapter)
|
77
75
|
return @default_plan if instance_variable_defined?(:@default_plan)
|
78
|
-
|
76
|
+
|
77
|
+
@default_plan = Clowne::Planner.compile(current_adapter, self)
|
79
78
|
end
|
80
79
|
|
81
|
-
def plan_with_traits(ids)
|
80
|
+
def plan_with_traits(ids, current_adapter: adapter)
|
82
81
|
# Cache plans for combinations of traits
|
83
82
|
traits_id = ids.map(&:to_s).join(':')
|
84
83
|
return traits_plans[traits_id] if traits_plans.key?(traits_id)
|
84
|
+
|
85
85
|
traits_plans[traits_id] = Clowne::Planner.compile(
|
86
|
-
self, traits: ids
|
86
|
+
current_adapter, self, traits: ids
|
87
87
|
)
|
88
88
|
end
|
89
89
|
|
@@ -93,8 +93,15 @@ module Clowne # :nodoc: all
|
|
93
93
|
|
94
94
|
private
|
95
95
|
|
96
|
+
def call_operation(adapter, object, plan, options)
|
97
|
+
adapter.class.operation_class.wrap(mapper: options.mapper) do
|
98
|
+
adapter.clone(object, plan, params: options.params)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
96
102
|
def traits_plans
|
97
103
|
return @traits_plans if instance_variable_defined?(:@traits_plans)
|
104
|
+
|
98
105
|
@traits_plans = {}
|
99
106
|
end
|
100
107
|
|
data/lib/clowne/declarations.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'clowne/dsl'
|
4
|
-
require 'clowne/plan'
|
4
|
+
require 'clowne/utils/plan'
|
5
5
|
|
6
6
|
module Clowne
|
7
7
|
module Declarations # :nodoc:
|
@@ -30,3 +30,4 @@ require 'clowne/declarations/finalize'
|
|
30
30
|
require 'clowne/declarations/include_association'
|
31
31
|
require 'clowne/declarations/nullify'
|
32
32
|
require 'clowne/declarations/trait'
|
33
|
+
require 'clowne/declarations/after_persist'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Declarations
|
5
|
+
class AfterPersist < Base # :nodoc: all
|
6
|
+
attr_reader :block
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
raise ArgumentError, 'Block is required for after_persist' unless block_given?
|
10
|
+
|
11
|
+
@block = Proc.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def compile(plan)
|
15
|
+
plan.add(:after_persist, self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Clowne::Declarations.add :after_persist, Clowne::Declarations::AfterPersist
|