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
data/docs/web/pages/en/index.js
CHANGED
@@ -82,7 +82,7 @@ class HomeSplash extends React.Component {
|
|
82
82
|
<div className="inner">
|
83
83
|
<ProjectTitle />
|
84
84
|
<PromoSection>
|
85
|
-
<Button href="
|
85
|
+
<Button href={docUrl("installation.html")}>Getting Started</Button>
|
86
86
|
</PromoSection>
|
87
87
|
</div>
|
88
88
|
</SplashContainer>
|
data/docs/web/sidebars.json
CHANGED
@@ -3,29 +3,35 @@
|
|
3
3
|
"Getting Started": [
|
4
4
|
"installation",
|
5
5
|
"basic_example",
|
6
|
-
"
|
6
|
+
"overview",
|
7
7
|
"alternatives"
|
8
8
|
],
|
9
9
|
"API": [
|
10
|
+
"operation",
|
10
11
|
"include_association",
|
11
12
|
"exclude_association",
|
12
13
|
"nullify",
|
13
14
|
"finalize",
|
15
|
+
"after_persist",
|
14
16
|
"init_as",
|
15
17
|
"traits",
|
16
|
-
"parameters"
|
17
|
-
"execution_order"
|
18
|
+
"parameters"
|
18
19
|
],
|
19
20
|
"Adapters": [
|
20
21
|
"supported_adapters",
|
21
22
|
"active_record",
|
22
23
|
"sequel"
|
23
24
|
],
|
24
|
-
"
|
25
|
+
"Advanced Options": [
|
25
26
|
"implicit_cloner",
|
26
27
|
"inline_configuration",
|
28
|
+
"clone_mapper",
|
27
29
|
"architecture",
|
30
|
+
"testing",
|
28
31
|
"customization"
|
32
|
+
],
|
33
|
+
"Upgrade Notes": [
|
34
|
+
"from_v02_to_v10"
|
29
35
|
]
|
30
36
|
}
|
31
37
|
}
|
data/docs/web/siteConfig.js
CHANGED
@@ -8,8 +8,9 @@
|
|
8
8
|
const siteConfig = {
|
9
9
|
title: 'Clowne' /* title for your website */,
|
10
10
|
tagline: 'A flexible gem for cloning your models',
|
11
|
-
url: '
|
12
|
-
|
11
|
+
url: 'http://clowne.evilmartians.io' /* your website url */,
|
12
|
+
cname: 'clowne.evilmartians.io',
|
13
|
+
baseUrl: '/' /* base url for your project */,
|
13
14
|
customDocsPath: '../docs',
|
14
15
|
projectName: 'clowne',
|
15
16
|
headerLinks: [
|
@@ -22,7 +23,7 @@ const siteConfig = {
|
|
22
23
|
favicon: 'img/favicon/favicon.ico',
|
23
24
|
/* colors for website */
|
24
25
|
colors: {
|
25
|
-
primaryColor: '#
|
26
|
+
primaryColor: '#9FA628', // '#ff5e5e'
|
26
27
|
secondaryColor: '#e3e3e3',
|
27
28
|
},
|
28
29
|
// This copyright info is used in /core/Footer.js and blog rss/atom feeds.
|
@@ -33,12 +34,13 @@ const siteConfig = {
|
|
33
34
|
// organizationName: 'deltice', // or set an env variable ORGANIZATION_NAME
|
34
35
|
highlight: {
|
35
36
|
// Highlight.js theme to use for syntax highlighting in code blocks
|
36
|
-
theme: '
|
37
|
+
theme: 'atom-one-light',
|
37
38
|
},
|
38
39
|
scripts: ['https://buttons.github.io/buttons.js'],
|
39
40
|
// You may provide arbitrary config keys to be used as needed by your template.
|
40
41
|
repoUrl: 'https://github.com/palkan/clowne',
|
41
42
|
gemUrl: 'https://rubygems.org/gems/clowne',
|
43
|
+
gaTrackingId: 'UA-104346673-2',
|
42
44
|
};
|
43
45
|
|
44
46
|
module.exports = siteConfig;
|
@@ -4,13 +4,13 @@
|
|
4
4
|
font-weight: 400;
|
5
5
|
font-style: normal;
|
6
6
|
font-family: "Stem Text";
|
7
|
-
src: url("
|
7
|
+
src: url("//cdn.evilmartians.com/front/fonts/subset-StemText-Regular.woff") format("woff");
|
8
8
|
}
|
9
9
|
@font-face {
|
10
10
|
font-weight: 700;
|
11
11
|
font-style: normal;
|
12
12
|
font-family: "Stem Text";
|
13
|
-
src: url("
|
13
|
+
src: url("//cdn.evilmartians.com/front/fonts/subset-StemText-Bold.woff") format("woff");
|
14
14
|
}
|
15
15
|
|
16
16
|
@font-face {
|
@@ -49,16 +49,15 @@ footer .sitemap div {
|
|
49
49
|
}
|
50
50
|
|
51
51
|
.fixedHeaderContainer a {
|
52
|
-
color: #
|
52
|
+
color: #9FA628;
|
53
53
|
}
|
54
54
|
|
55
55
|
header h2 {
|
56
|
-
color: #
|
56
|
+
color: #9FA628;
|
57
57
|
text-transform: uppercase;
|
58
58
|
}
|
59
59
|
|
60
60
|
.container .wrapper h2 {
|
61
|
-
color: #111; /* #ff5e5e; */
|
62
61
|
font-weight: bold;
|
63
62
|
}
|
64
63
|
|
@@ -66,6 +65,14 @@ header h2 {
|
|
66
65
|
text-decoration: underline;
|
67
66
|
}
|
68
67
|
|
68
|
+
.projectTitle {
|
69
|
+
color: #111;
|
70
|
+
}
|
71
|
+
|
72
|
+
.projectTitleName {
|
73
|
+
color: #9FA628;
|
74
|
+
}
|
75
|
+
|
69
76
|
.mainContainer .wrapper a:hover {
|
70
77
|
text-decoration: none;
|
71
78
|
}
|
@@ -79,7 +86,7 @@ small {
|
|
79
86
|
}
|
80
87
|
|
81
88
|
.navigationSlider .slidingNav ul li a {
|
82
|
-
color: #
|
89
|
+
color: #9FA628;
|
83
90
|
}
|
84
91
|
|
85
92
|
nav.toc .toggleNav .navGroup.navGroupActive {
|
@@ -91,9 +98,8 @@ nav.toc .toggleNav .navGroup.navGroupActive {
|
|
91
98
|
margin-bottom: 30px;
|
92
99
|
}
|
93
100
|
|
94
|
-
|
95
|
-
|
96
|
-
color: #a7a7a7;
|
101
|
+
.hljs-doctag {
|
102
|
+
color: #a0a1a7;
|
97
103
|
}
|
98
104
|
|
99
105
|
@keyframes humanoids-blink{
|
@@ -226,4 +232,4 @@ footer h5{
|
|
226
232
|
.mainContainer {
|
227
233
|
background: none;
|
228
234
|
border-top: 1px solid #e3e3e3;
|
229
|
-
}
|
235
|
+
}
|
data/gemfiles/jruby.gemfile
CHANGED
data/lib/clowne.rb
CHANGED
@@ -9,11 +9,13 @@ require 'clowne/adapters/base'
|
|
9
9
|
# Declarative models cloning
|
10
10
|
module Clowne
|
11
11
|
# List of built-in adapters
|
12
|
+
# rubocop:disable AlignHash
|
12
13
|
ADAPTERS = {
|
13
14
|
base: 'Base',
|
14
15
|
active_record: 'ActiveRecord',
|
15
16
|
sequel: 'Sequel'
|
16
17
|
}.freeze
|
18
|
+
# rubocop:enable AlignHash
|
17
19
|
|
18
20
|
class << self
|
19
21
|
attr_reader :default_adapter, :raise_on_override
|
@@ -29,6 +31,7 @@ module Clowne
|
|
29
31
|
elsif adapter.is_a?(Symbol)
|
30
32
|
adapter_class = ADAPTERS[adapter]
|
31
33
|
raise "Unknown adapter: #{adapter}" if adapter_class.nil?
|
34
|
+
|
32
35
|
Clowne::Adapters.const_get(adapter_class).new
|
33
36
|
else
|
34
37
|
adapter
|
@@ -5,8 +5,7 @@ require 'clowne/ext/orm_ext'
|
|
5
5
|
module Clowne
|
6
6
|
module Adapters
|
7
7
|
# Cloning adapter for ActiveRecord
|
8
|
-
class ActiveRecord < Base
|
9
|
-
end
|
8
|
+
class ActiveRecord < Base; end
|
10
9
|
end
|
11
10
|
end
|
12
11
|
|
@@ -15,4 +14,4 @@ ActiveSupport.on_load(:active_record) do
|
|
15
14
|
end
|
16
15
|
|
17
16
|
require 'clowne/adapters/active_record/associations'
|
18
|
-
require 'clowne/adapters/active_record/association'
|
17
|
+
require 'clowne/adapters/active_record/resolvers/association'
|
@@ -9,7 +9,8 @@ module Clowne
|
|
9
9
|
def call(record)
|
10
10
|
child = association
|
11
11
|
return record unless child
|
12
|
-
|
12
|
+
|
13
|
+
unless declaration.scope.nil?
|
13
14
|
warn(
|
14
15
|
'[Clowne] Has one association does not support scopes ' \
|
15
16
|
"(#{@association_name} for #{@source.class})"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clowne
|
4
|
+
module Adapters # :nodoc: all
|
5
|
+
class ActiveRecord
|
6
|
+
module Resolvers
|
7
|
+
class UnknownAssociation < StandardError; end
|
8
|
+
|
9
|
+
class Association
|
10
|
+
class << self
|
11
|
+
# rubocop: disable Metrics/ParameterLists
|
12
|
+
def call(source, record, declaration, adapter:, params:, **_options)
|
13
|
+
reflection = source.class.reflections[declaration.name.to_s]
|
14
|
+
|
15
|
+
if reflection.nil?
|
16
|
+
raise UnknownAssociation,
|
17
|
+
"Association #{declaration.name} couldn't be found for #{source.class}"
|
18
|
+
end
|
19
|
+
|
20
|
+
cloner_class = Associations.cloner_for(reflection)
|
21
|
+
|
22
|
+
cloner_class.new(reflection, source, declaration, adapter, params).call(record)
|
23
|
+
|
24
|
+
record
|
25
|
+
end
|
26
|
+
# rubocop: enable Metrics/ParameterLists
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Clowne::Adapters::ActiveRecord.register_resolver(
|
35
|
+
:association,
|
36
|
+
Clowne::Adapters::ActiveRecord::Resolvers::Association,
|
37
|
+
before: :nullify
|
38
|
+
)
|
data/lib/clowne/adapters/base.rb
CHANGED
@@ -2,46 +2,34 @@
|
|
2
2
|
|
3
3
|
require 'clowne/adapters/registry'
|
4
4
|
|
5
|
+
require 'clowne/resolvers/init_as'
|
6
|
+
require 'clowne/resolvers/nullify'
|
7
|
+
require 'clowne/resolvers/finalize'
|
8
|
+
require 'clowne/resolvers/after_persist'
|
9
|
+
|
5
10
|
module Clowne
|
6
11
|
module Adapters
|
7
12
|
# ORM-independant adapter (just calls #dup).
|
8
13
|
# Works with nullify/finalize.
|
9
14
|
class Base
|
10
|
-
|
11
|
-
attr_reader :registry
|
12
|
-
|
13
|
-
def inherited(subclass)
|
14
|
-
# Duplicate registry
|
15
|
-
subclass.registry = registry.dup
|
16
|
-
end
|
17
|
-
|
18
|
-
def resolver_for(type)
|
19
|
-
registry.mapping[type] || raise("Uknown resolver #{type} for #{self}")
|
20
|
-
end
|
15
|
+
include Clowne::Adapters::Registry::Container
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
registry.insert_before before, type
|
31
|
-
else
|
32
|
-
registry.append type
|
17
|
+
class << self
|
18
|
+
# Duplicate record and remember record <-> clone relationship in operation
|
19
|
+
# Cab be overrided in special adapter
|
20
|
+
# +record+:: Instance of record (ActiveRecord or Sequel)
|
21
|
+
def dup_record(record)
|
22
|
+
record.dup.tap do |clone|
|
23
|
+
operation = operation_class.current
|
24
|
+
operation.add_mapping(record, clone)
|
33
25
|
end
|
34
26
|
end
|
35
27
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
self.registry = Registry.new
|
42
|
-
|
43
|
-
def registry
|
44
|
-
self.class.registry
|
28
|
+
# Operation class which using for cloning
|
29
|
+
# Cab be overrided in special adapter
|
30
|
+
def operation_class
|
31
|
+
Clowne::Utils::Operation
|
32
|
+
end
|
45
33
|
end
|
46
34
|
|
47
35
|
# Using a plan make full duplicate of record
|
@@ -50,19 +38,13 @@ module Clowne
|
|
50
38
|
# +params+:: Custom params hash
|
51
39
|
def clone(source, plan, params: {})
|
52
40
|
declarations = plan.declarations
|
53
|
-
|
41
|
+
init_record = init_record(self.class.dup_record(source))
|
42
|
+
|
43
|
+
declarations.inject(init_record) do |record, (type, declaration)|
|
54
44
|
resolver_for(type).call(source, record, declaration, params: params, adapter: self)
|
55
45
|
end
|
56
46
|
end
|
57
47
|
|
58
|
-
def resolver_for(type)
|
59
|
-
self.class.resolver_for(type)
|
60
|
-
end
|
61
|
-
|
62
|
-
def dup_source(source)
|
63
|
-
source.dup
|
64
|
-
end
|
65
|
-
|
66
48
|
def init_record(record)
|
67
49
|
# Override in custom adapters
|
68
50
|
record
|
@@ -71,6 +53,23 @@ module Clowne
|
|
71
53
|
end
|
72
54
|
end
|
73
55
|
|
74
|
-
|
75
|
-
|
76
|
-
|
56
|
+
Clowne::Adapters::Base.register_resolver(
|
57
|
+
:init_as,
|
58
|
+
Clowne::Resolvers::InitAs,
|
59
|
+
prepend: true
|
60
|
+
)
|
61
|
+
|
62
|
+
Clowne::Adapters::Base.register_resolver(
|
63
|
+
:nullify,
|
64
|
+
Clowne::Resolvers::Nullify
|
65
|
+
)
|
66
|
+
|
67
|
+
Clowne::Adapters::Base.register_resolver(
|
68
|
+
:finalize, Clowne::Resolvers::Finalize,
|
69
|
+
after: :nullify
|
70
|
+
)
|
71
|
+
|
72
|
+
Clowne::Adapters::Base.register_resolver(
|
73
|
+
:after_persist, Clowne::Resolvers::AfterPersist,
|
74
|
+
after: :finalize
|
75
|
+
)
|
@@ -9,16 +9,15 @@ module Clowne
|
|
9
9
|
# +source+:: Instance of cloned object (ex: User.new(posts: posts))
|
10
10
|
# +declaration+:: = Relation description
|
11
11
|
# (ex: Clowne::Declarations::IncludeAssociation.new(:posts))
|
12
|
+
# +adapter+:: Clowne adapter
|
12
13
|
# +params+:: = Instance of Hash
|
13
|
-
def initialize(reflection, source, declaration, params)
|
14
|
+
def initialize(reflection, source, declaration, adapter, params)
|
14
15
|
@source = source
|
15
|
-
@
|
16
|
-
@
|
16
|
+
@declaration = declaration
|
17
|
+
@adapter = adapter
|
17
18
|
@params = params
|
18
19
|
@association_name = declaration.name.to_s
|
19
20
|
@reflection = reflection
|
20
|
-
@cloner_options = declaration.params_proxy.permit(params: params, parent: source)
|
21
|
-
@cloner_options.merge!(traits: declaration.traits) if declaration.traits
|
22
21
|
end
|
23
22
|
|
24
23
|
def call(_record)
|
@@ -31,24 +30,24 @@ module Clowne
|
|
31
30
|
|
32
31
|
def clone_one(child)
|
33
32
|
cloner = cloner_for(child)
|
34
|
-
cloner ? cloner.call(child, cloner_options) :
|
33
|
+
cloner ? cloner.call(child, cloner_options) : dup_record(child)
|
35
34
|
end
|
36
35
|
|
37
36
|
def with_scope
|
38
|
-
|
37
|
+
scope = declaration.scope
|
39
38
|
if scope.is_a?(Symbol)
|
40
|
-
|
39
|
+
init_scope.__send__(scope)
|
41
40
|
elsif scope.is_a?(Proc)
|
42
|
-
|
41
|
+
init_scope.instance_exec(params, &scope) || init_scope
|
43
42
|
else
|
44
|
-
|
43
|
+
init_scope
|
45
44
|
end.to_a
|
46
45
|
end
|
47
46
|
|
48
47
|
private
|
49
48
|
|
50
|
-
def
|
51
|
-
|
49
|
+
def dup_record(record)
|
50
|
+
adapter.class.dup_record(record)
|
52
51
|
end
|
53
52
|
|
54
53
|
def init_scope
|
@@ -56,13 +55,23 @@ module Clowne
|
|
56
55
|
end
|
57
56
|
|
58
57
|
def cloner_for(child)
|
59
|
-
return clone_with if clone_with
|
58
|
+
return declaration.clone_with if declaration.clone_with
|
60
59
|
|
61
60
|
return child.class.cloner_class if child.class.respond_to?(:cloner_class)
|
62
61
|
end
|
63
62
|
|
64
|
-
|
65
|
-
|
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
|
66
75
|
end
|
67
76
|
end
|
68
77
|
end
|