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
@@ -25,7 +25,7 @@ module Clowne
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def params_proxy
|
28
|
-
@_params_proxy ||= Clowne::Params.proxy(options[:params])
|
28
|
+
@_params_proxy ||= Clowne::Utils::Params.proxy(options[:params])
|
29
29
|
end
|
30
30
|
|
31
31
|
def params
|
@@ -34,6 +34,7 @@ module Clowne
|
|
34
34
|
|
35
35
|
def clone_with
|
36
36
|
return @clone_with if instance_variable_defined?(:@clone_with)
|
37
|
+
|
37
38
|
@clone_with =
|
38
39
|
case options[:clone_with]
|
39
40
|
when String, Symbol
|
data/lib/clowne/dsl.rb
CHANGED
@@ -5,10 +5,19 @@ module Clowne
|
|
5
5
|
def adapter(adapter = nil)
|
6
6
|
if adapter.nil?
|
7
7
|
return @_adapter if instance_variable_defined?(:@_adapter)
|
8
|
+
|
8
9
|
@_adapter = Clowne.default_adapter
|
9
10
|
else
|
10
11
|
@_adapter = Clowne.resolve_adapter(adapter)
|
11
12
|
end
|
12
13
|
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def current_adapter(user_adapter)
|
18
|
+
return adapter if user_adapter.nil?
|
19
|
+
|
20
|
+
Clowne.resolve_adapter(user_adapter)
|
21
|
+
end
|
13
22
|
end
|
14
23
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Ext # :nodoc: all
|
5
|
+
# Add yield_self and then if missing
|
6
|
+
module YieldSelfThen
|
7
|
+
module Ext
|
8
|
+
unless nil.respond_to?(:yield_self)
|
9
|
+
def yield_self
|
10
|
+
yield self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
alias then yield_self
|
15
|
+
end
|
16
|
+
|
17
|
+
# See https://github.com/jruby/jruby/issues/5220
|
18
|
+
::Object.include(Ext) if RUBY_PLATFORM =~ /java/i
|
19
|
+
|
20
|
+
refine Object do
|
21
|
+
include Ext
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/clowne/planner.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'clowne/plan'
|
3
|
+
require 'clowne/utils/plan'
|
4
4
|
|
5
5
|
module Clowne
|
6
6
|
class Planner # :nodoc: all
|
7
7
|
class << self
|
8
8
|
# Compile plan for cloner with traits
|
9
|
-
def compile(cloner, traits: nil)
|
9
|
+
def compile(adapter, cloner, traits: nil)
|
10
10
|
declarations = cloner.declarations.dup
|
11
11
|
|
12
12
|
declarations += compile_traits(cloner, traits) unless traits.nil?
|
13
13
|
|
14
|
-
declarations.each_with_object(
|
14
|
+
declarations.each_with_object(
|
15
|
+
Utils::Plan.new(adapter.registry)
|
16
|
+
) do |declaration, plan|
|
15
17
|
declaration.compile(plan)
|
16
18
|
end
|
17
19
|
end
|
@@ -42,6 +44,7 @@ module Clowne
|
|
42
44
|
traits.map do |id|
|
43
45
|
trait = cloner.traits[id]
|
44
46
|
raise ConfigurationError, "Trait not found: #{id}" if trait.nil?
|
47
|
+
|
45
48
|
trait.compiled
|
46
49
|
end.flatten
|
47
50
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
class Resolvers
|
5
|
+
module AfterPersist # :nodoc: all
|
6
|
+
def self.call(source, record, declaration, params:, **_options)
|
7
|
+
operation = Clowne::Utils::Operation.current
|
8
|
+
params ||= {}
|
9
|
+
operation.add_after_persist(
|
10
|
+
proc do
|
11
|
+
declaration.block.call(source, record, params.merge(mapper: operation.mapper))
|
12
|
+
end
|
13
|
+
)
|
14
|
+
record
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
class Resolvers
|
5
|
+
module InitAs # :nodoc: all
|
6
|
+
# rubocop: disable Metrics/ParameterLists
|
7
|
+
def self.call(source, _record, declaration, params:, adapter:, **_options)
|
8
|
+
adapter.init_record(declaration.block.call(source, **params))
|
9
|
+
end
|
10
|
+
# rubocop: enable Metrics/ParameterLists
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
class Resolvers
|
5
|
+
module Nullify # :nodoc: all
|
6
|
+
def self.call(_source, record, declaration, **_options)
|
7
|
+
declaration.attributes.each do |attr|
|
8
|
+
record.__send__("#{attr}=", nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
record
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/clowne/rspec/helpers.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clowne/ext/record_key'
|
4
|
+
|
5
|
+
module Clowne
|
6
|
+
module Utils
|
7
|
+
class CloneMapper # :nodoc: all
|
8
|
+
def initialize
|
9
|
+
@store = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(origin, clone)
|
13
|
+
@store[origin] ||= clone
|
14
|
+
end
|
15
|
+
|
16
|
+
def clone_of(origin)
|
17
|
+
@store[origin]
|
18
|
+
end
|
19
|
+
|
20
|
+
def origin_of(clone)
|
21
|
+
origin, _clone = @store.rassoc(clone)
|
22
|
+
origin
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clowne/utils/clone_mapper'
|
4
|
+
|
5
|
+
module Clowne
|
6
|
+
module Utils
|
7
|
+
class Operation # :nodoc: all
|
8
|
+
THREAD_KEY = :"#{name}.clowne_operation"
|
9
|
+
DEFAULT_MAPPER = Utils::CloneMapper
|
10
|
+
|
11
|
+
private_constant :THREAD_KEY
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def current
|
15
|
+
Thread.current[THREAD_KEY]
|
16
|
+
end
|
17
|
+
|
18
|
+
def wrap(mapper: nil)
|
19
|
+
return yield if current
|
20
|
+
|
21
|
+
Thread.current[THREAD_KEY] = new(mapper || DEFAULT_MAPPER.new)
|
22
|
+
|
23
|
+
current.tap do |operation|
|
24
|
+
operation.clone = yield
|
25
|
+
clear!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear!
|
30
|
+
Thread.current[THREAD_KEY] = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_writer :clone
|
35
|
+
attr_reader :mapper
|
36
|
+
|
37
|
+
def initialize(mapper)
|
38
|
+
@blocks = []
|
39
|
+
@mapper = mapper
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_after_persist(block)
|
43
|
+
@blocks.unshift(block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_mapping(origin, clone)
|
47
|
+
@mapper.add(origin, clone)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_record
|
51
|
+
@clone
|
52
|
+
end
|
53
|
+
|
54
|
+
def persist!
|
55
|
+
to_record.save!.tap do
|
56
|
+
run_after_persist
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def persist
|
61
|
+
to_record.save.tap do |result|
|
62
|
+
next unless result
|
63
|
+
|
64
|
+
run_after_persist
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def save
|
69
|
+
warn '[DEPRECATION] `save` is deprecated. Please use `persist` instead.'
|
70
|
+
@clone.save
|
71
|
+
end
|
72
|
+
|
73
|
+
def save!
|
74
|
+
warn '[DEPRECATION] `save!` is deprecated. Please use `persist!` instead.'
|
75
|
+
@clone.save!
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_after_persist
|
79
|
+
@blocks.each(&:call)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Utils # :nodoc: all
|
5
|
+
class Options
|
6
|
+
INTERNAL_KEYS = %i[adapter traits clowne_only_actions mapping only].freeze
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def traits
|
13
|
+
@_traits ||= Array(options[:traits])
|
14
|
+
end
|
15
|
+
|
16
|
+
def only
|
17
|
+
options[:clowne_only_actions]
|
18
|
+
end
|
19
|
+
|
20
|
+
def mapper
|
21
|
+
options[:mapper]
|
22
|
+
end
|
23
|
+
|
24
|
+
def adapter
|
25
|
+
options[:adapter]
|
26
|
+
end
|
27
|
+
|
28
|
+
def params
|
29
|
+
options.dup.tap do |o|
|
30
|
+
INTERNAL_KEYS.each { |key| o.delete(key) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :options
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clowne/ext/lambda_as_proc'
|
4
|
+
|
5
|
+
module Clowne
|
6
|
+
module Utils
|
7
|
+
class Params # :nodoc: all
|
8
|
+
class BaseProxy
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def permit(_params)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class PassProxy < BaseProxy
|
21
|
+
def permit(params:, **)
|
22
|
+
params
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class NullProxy < BaseProxy
|
27
|
+
def permit(_params)
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class BlockProxy < BaseProxy
|
33
|
+
using Clowne::Ext::LambdaAsProc
|
34
|
+
|
35
|
+
def permit(params:, parent:)
|
36
|
+
value.to_proc.call(params, parent)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class KeyProxy < BaseProxy
|
41
|
+
def permit(params:, **)
|
42
|
+
nested_params = params.fetch(value)
|
43
|
+
return nested_params if nested_params.is_a?(Hash)
|
44
|
+
|
45
|
+
raise KeyError, "value by key '#{value}' must be a Hash"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def proxy(value)
|
51
|
+
if value == true
|
52
|
+
PassProxy
|
53
|
+
elsif value.nil? || value == false
|
54
|
+
NullProxy
|
55
|
+
elsif value.is_a?(Proc)
|
56
|
+
BlockProxy
|
57
|
+
else
|
58
|
+
KeyProxy
|
59
|
+
end.new(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Utils
|
5
|
+
class Plan # :nodoc: all
|
6
|
+
class TwoPhaseSet
|
7
|
+
def initialize
|
8
|
+
@added = {}
|
9
|
+
@removed = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(k, v)
|
13
|
+
return if @removed.include?(k)
|
14
|
+
|
15
|
+
@added[k] = v
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(k)
|
19
|
+
return if @removed.include?(k)
|
20
|
+
|
21
|
+
@removed << k
|
22
|
+
@added.delete(k)
|
23
|
+
end
|
24
|
+
|
25
|
+
def values
|
26
|
+
@added.values
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(registry)
|
31
|
+
@registry = registry
|
32
|
+
@data = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(type, declaration)
|
36
|
+
data[type] = [] unless data.key?(type)
|
37
|
+
data[type] << declaration
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_to(type, id, declaration)
|
41
|
+
data[type] = TwoPhaseSet.new unless data.key?(type)
|
42
|
+
data[type][id] = declaration
|
43
|
+
end
|
44
|
+
|
45
|
+
def set(type, declaration)
|
46
|
+
data[type] = declaration
|
47
|
+
end
|
48
|
+
|
49
|
+
def get(type)
|
50
|
+
data[type]
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove(type)
|
54
|
+
data.delete(type)
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_from(type, id)
|
58
|
+
return unless data[type]
|
59
|
+
|
60
|
+
data[type].delete(id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def declarations(reload = false)
|
64
|
+
return @declarations if !reload && instance_variable_defined?(:@declarations)
|
65
|
+
|
66
|
+
@declarations =
|
67
|
+
registry.actions.flat_map do |type|
|
68
|
+
value = data[type]
|
69
|
+
next if value.nil?
|
70
|
+
|
71
|
+
value = value.values if value.is_a?(TwoPhaseSet)
|
72
|
+
value = Array(value)
|
73
|
+
value.map { |v| [type, v] }
|
74
|
+
end.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def dup
|
78
|
+
self.class.new(registry).tap do |duped|
|
79
|
+
data.each do |k, v|
|
80
|
+
duped.set(k, v.dup)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
attr_reader :data, :registry
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|