clowne 1.1.0 → 1.2.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 +11 -51
- data/.travis.yml +11 -23
- data/Gemfile +9 -9
- data/README.md +8 -7
- data/Rakefile +3 -3
- data/clowne.gemspec +14 -8
- data/docs/.nojekyll +0 -0
- data/docs/.rubocop.yml +8 -2
- data/docs/CNAME +1 -0
- data/docs/README.md +131 -0
- data/docs/_sidebar.md +25 -0
- data/docs/active_record.md +2 -5
- data/docs/after_clone.md +3 -6
- data/docs/after_persist.md +7 -10
- data/docs/architecture.md +2 -5
- data/docs/assets/docsify.min.js +1 -0
- data/docs/assets/prism-ruby.min.js +1 -0
- data/docs/assets/styles.css +348 -0
- data/docs/assets/vue.css +1 -0
- data/docs/clone_mapper.md +2 -5
- data/docs/customization.md +1 -4
- data/docs/exclude_association.md +1 -4
- data/docs/finalize.md +4 -8
- data/docs/from_v02_to_v1.md +2 -10
- data/docs/getting_started.md +171 -0
- data/docs/implicit_cloner.md +1 -4
- data/docs/include_association.md +3 -6
- data/docs/index.html +29 -0
- data/docs/init_as.md +4 -8
- data/docs/inline_configuration.md +1 -4
- data/docs/nullify.md +1 -5
- data/docs/operation.md +3 -6
- data/docs/parameters.md +5 -8
- data/docs/sequel.md +1 -4
- data/docs/supported_adapters.md +3 -6
- data/docs/testing.md +18 -21
- data/docs/traits.md +1 -4
- data/gemfiles/activerecord42.gemfile +5 -5
- data/gemfiles/jruby.gemfile +6 -6
- data/gemfiles/railsmaster.gemfile +6 -6
- data/lib/clowne.rb +11 -11
- data/lib/clowne/adapters/active_record.rb +3 -3
- data/lib/clowne/adapters/active_record/associations.rb +7 -7
- data/lib/clowne/adapters/active_record/associations/base.rb +1 -1
- data/lib/clowne/adapters/active_record/associations/belongs_to.rb +1 -1
- data/lib/clowne/adapters/active_record/associations/has_one.rb +1 -1
- data/lib/clowne/adapters/active_record/resolvers/association.rb +1 -1
- data/lib/clowne/adapters/base.rb +6 -6
- data/lib/clowne/adapters/sequel.rb +7 -7
- data/lib/clowne/adapters/sequel/associations.rb +6 -6
- data/lib/clowne/adapters/sequel/associations/base.rb +2 -2
- data/lib/clowne/adapters/sequel/associations/many_to_many.rb +4 -4
- data/lib/clowne/adapters/sequel/associations/one_to_many.rb +1 -1
- data/lib/clowne/adapters/sequel/associations/one_to_one.rb +1 -1
- data/lib/clowne/adapters/sequel/operation.rb +3 -2
- data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +1 -1
- data/lib/clowne/adapters/sequel/resolvers/association.rb +1 -1
- data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +1 -1
- data/lib/clowne/cloner.rb +8 -8
- data/lib/clowne/declarations.rb +15 -15
- data/lib/clowne/declarations/after_clone.rb +1 -1
- data/lib/clowne/declarations/after_persist.rb +1 -1
- data/lib/clowne/declarations/finalize.rb +1 -1
- data/lib/clowne/declarations/include_association.rb +1 -1
- data/lib/clowne/declarations/init_as.rb +1 -1
- data/lib/clowne/declarations/nullify.rb +1 -1
- data/lib/clowne/ext/orm_ext.rb +1 -1
- data/lib/clowne/ext/record_key.rb +1 -1
- data/lib/clowne/ext/string_constantize.rb +1 -1
- data/lib/clowne/ext/yield_self_then.rb +1 -1
- data/lib/clowne/planner.rb +1 -1
- data/lib/clowne/rspec.rb +3 -3
- data/lib/clowne/rspec/clone_association.rb +3 -3
- data/lib/clowne/rspec/clone_associations.rb +2 -2
- data/lib/clowne/rspec/helpers.rb +1 -1
- data/lib/clowne/utils/clone_mapper.rb +1 -1
- data/lib/clowne/utils/operation.rb +3 -3
- data/lib/clowne/utils/params.rb +1 -1
- data/lib/clowne/version.rb +1 -1
- metadata +50 -35
- data/docs/alternatives.md +0 -26
- data/docs/basic_example.md +0 -83
- data/docs/installation.md +0 -46
- data/docs/overview.md +0 -25
- data/docs/web/.gitignore +0 -11
- data/docs/web/README.md +0 -6
- data/docs/web/core/Footer.js +0 -88
- data/docs/web/i18n/en.json +0 -141
- data/docs/web/package.json +0 -14
- data/docs/web/pages/en/help.js +0 -50
- data/docs/web/pages/en/index.js +0 -231
- data/docs/web/pages/en/users.js +0 -47
- data/docs/web/sidebars.json +0 -38
- data/docs/web/siteConfig.js +0 -46
- data/docs/web/static/css/custom.css +0 -235
- data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
- data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
- data/docs/web/static/img/favicon/favicon.ico +0 -0
- data/docs/web/yarn.lock +0 -1741
data/docs/assets/vue.css
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:5}.search .search-keyword,.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}li input[type=checkbox]{margin:0 .2em .25em 0;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:2}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:0;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:a .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:3}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0 0 0 15px;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:4}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee;width:1px;min-width:100%}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}.markdown-section ul.task-list>li{list-style-type:none}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:a .56s ease-in-out}}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{-ms-flex-align:center;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover.show{display:-ms-flexbox;display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{-ms-flex:1;flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border:1px solid var(--theme-color,#42b983);border-radius:2rem;box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.function,.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0}
|
data/docs/clone_mapper.md
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
|
2
|
-
id: clone_mapper
|
3
|
-
title: Clone mapper
|
4
|
-
---
|
1
|
+
# Clone mapper
|
5
2
|
|
6
|
-
|
3
|
+
*Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter.*
|
7
4
|
|
8
5
|
In [`after_persist`](after_persist.md) documenation you can find interisting code:
|
9
6
|
|
data/docs/customization.md
CHANGED
data/docs/exclude_association.md
CHANGED
data/docs/finalize.md
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
-
|
2
|
-
id: finalize
|
3
|
-
title: Finalization
|
4
|
-
sidebar_label: Finalize
|
5
|
-
---
|
1
|
+
# Finalization
|
6
2
|
|
7
3
|
To apply custom transformations to the cloned record, you can use the `finalize` declaration:
|
8
4
|
|
9
5
|
```ruby
|
10
6
|
class UserCloner < Clowne::Cloner
|
11
7
|
finalize do |_source, record, _params|
|
12
|
-
record.name =
|
8
|
+
record.name = "This is copy!"
|
13
9
|
end
|
14
10
|
|
15
11
|
trait :change_email do
|
@@ -22,7 +18,7 @@ end
|
|
22
18
|
cloned = UserCloner.call(user).to_record
|
23
19
|
cloned.name
|
24
20
|
# => 'This is copy!'
|
25
|
-
cloned.email ==
|
21
|
+
cloned.email == "clone@example.com"
|
26
22
|
# => false
|
27
23
|
|
28
24
|
cloned2 = UserCloner.call(user, traits: :change_email).to_record
|
@@ -32,4 +28,4 @@ cloned2.email
|
|
32
28
|
# => 'clone@example.com'
|
33
29
|
```
|
34
30
|
|
35
|
-
Finalization blocks are called at the end of the [cloning process](
|
31
|
+
Finalization blocks are called at the end of the [cloning process](getting_started?id=execution-order).
|
data/docs/from_v02_to_v1.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: from_v02_to_v10
|
3
|
-
title: From v0.2.x to v1.0.0
|
4
|
-
---
|
1
|
+
# From v0.2.x to v1.0.0
|
5
2
|
|
6
3
|
The breaking change of v1.0 is the return of a unified [`result object`](operation.md) for all adapters.
|
7
4
|
|
@@ -37,9 +34,8 @@ clone.persisted?
|
|
37
34
|
|
38
35
|
### Move post-processing cloning logic into [`after_persist`](after_persist.md) callback (if you have it)
|
39
36
|
|
40
|
-
|
37
|
+
*Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter.*
|
41
38
|
|
42
|
-
<span style="display:none;"># rubocop:disable all</span>
|
43
39
|
```ruby
|
44
40
|
# Before
|
45
41
|
clone = UserCloner.call(user)
|
@@ -56,8 +52,6 @@ end
|
|
56
52
|
|
57
53
|
clone = UserCloner.call(user).tap(&:persist).to_record
|
58
54
|
```
|
59
|
-
<span style="display:none;"># rubocop:enable all</span>
|
60
|
-
|
61
55
|
## Sequel
|
62
56
|
|
63
57
|
### Use `to_record` instead of `to_model`
|
@@ -78,7 +72,6 @@ clone.new?
|
|
78
72
|
|
79
73
|
### Use `operation#persist` instead of converting to model and calling `#save`
|
80
74
|
|
81
|
-
<span style="display:none;"># rubocop:disable all</span>
|
82
75
|
```ruby
|
83
76
|
# Before
|
84
77
|
record_wrapper = UserCloner.call(user)
|
@@ -88,4 +81,3 @@ clone.save
|
|
88
81
|
# After
|
89
82
|
clone = UserCloner.call(user).tap(&:persist).to_record
|
90
83
|
```
|
91
|
-
<span style="display:none;"># rubocop:enable all</span>
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
To install Clowne with RubyGems:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem install clowne
|
9
|
+
```
|
10
|
+
|
11
|
+
Or add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "clowne"
|
15
|
+
```
|
16
|
+
|
17
|
+
## Configuration
|
18
|
+
|
19
|
+
Basic cloner implementation looks like:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class SomeCloner < Clowne::Cloner
|
23
|
+
adapter :active_record # or adapter Clowne::Adapters::ActiveRecord
|
24
|
+
# some implementation ...
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
You can configure the default adapter for cloners:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# put to initializer
|
32
|
+
# e.g. config/initializers/clowne.rb
|
33
|
+
Clowne.default_adapter = :active_record
|
34
|
+
```
|
35
|
+
|
36
|
+
and skip explicit adapter declaration
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class SomeCloner < Clowne::Cloner
|
40
|
+
# some implementation ...
|
41
|
+
end
|
42
|
+
```
|
43
|
+
See the list of [available adapters](supported_adapters.md).
|
44
|
+
|
45
|
+
## Basic Example
|
46
|
+
|
47
|
+
Assume that you have the following model:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class User < ActiveRecord::Base
|
51
|
+
# create_table :users do |t|
|
52
|
+
# t.string :login
|
53
|
+
# t.string :email
|
54
|
+
# t.timestamps null: false
|
55
|
+
# end
|
56
|
+
|
57
|
+
has_one :profile
|
58
|
+
has_many :posts
|
59
|
+
end
|
60
|
+
|
61
|
+
class Profile < ActiveRecord::Base
|
62
|
+
# create_table :profiles do |t|
|
63
|
+
# t.string :name
|
64
|
+
# end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Post < ActiveRecord::Base
|
68
|
+
# create_table :posts
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Let's declare our cloners first:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class UserCloner < Clowne::Cloner
|
76
|
+
adapter :active_record
|
77
|
+
|
78
|
+
include_association :profile, clone_with: SpecialProfileCloner
|
79
|
+
include_association :posts
|
80
|
+
|
81
|
+
nullify :login
|
82
|
+
|
83
|
+
# params here is an arbitrary Hash passed into cloner
|
84
|
+
finalize do |_source, record, params|
|
85
|
+
record.email = params[:email]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class SpecialProfileCloner < Clowne::Cloner
|
90
|
+
adapter :active_record
|
91
|
+
|
92
|
+
nullify :name
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Now you can use `UserCloner` to clone existing records:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
user = User.last
|
100
|
+
# => <#User id: 1, login: 'clown', email: 'clown@circus.example.com'>
|
101
|
+
|
102
|
+
operation = UserCloner.call(user, email: "fake@example.com")
|
103
|
+
# => <#Clowne::Utils::Operation...>
|
104
|
+
|
105
|
+
operation.to_record
|
106
|
+
# => <#User id: nil, login: nil, email: 'fake@example.com'>
|
107
|
+
|
108
|
+
operation.persist!
|
109
|
+
# => true
|
110
|
+
|
111
|
+
cloned = operation.to_record
|
112
|
+
# => <#User id: 2, login: nil, email: 'fake@example.com'>
|
113
|
+
|
114
|
+
cloned.login
|
115
|
+
# => nil
|
116
|
+
cloned.email
|
117
|
+
# => "fake@example.com"
|
118
|
+
|
119
|
+
# associations:
|
120
|
+
cloned.posts.count == user.posts.count
|
121
|
+
# => true
|
122
|
+
cloned.profile.name
|
123
|
+
# => nil
|
124
|
+
```
|
125
|
+
|
126
|
+
## Overview
|
127
|
+
|
128
|
+
In [the basic example](#basic-example), you can see that Clowne consists of flexible DSL which is used in a class inherited of `Clowne::Cloner`.
|
129
|
+
|
130
|
+
You can combinate this DSL via [`traits`](traits.md) and make a cloning plan which exactly you want.
|
131
|
+
|
132
|
+
**We strongly recommend [`write tests`](testing.md) to cover resulting cloner logic**
|
133
|
+
|
134
|
+
Cloner class returns [`Operation`](operation.md) instance as a result of cloning. The operation provides methods to save cloned record. You can wrap this call to a transaction if it is necessary.
|
135
|
+
|
136
|
+
### Execution Order
|
137
|
+
|
138
|
+
The order of cloning actions depends on the adapter (i.e., could be customized).
|
139
|
+
|
140
|
+
All built-in adapters have the same order and what happens when you call `Operation#persist`:
|
141
|
+
- init clone (see [`init_as`](init_as.md)) (empty by default)
|
142
|
+
- [`clone associations`](include_association.md)
|
143
|
+
- [`nullify`](nullify.md) attributes
|
144
|
+
- run [`finalize`](finalize.md) blocks. _The order of [`finalize`](finalize.md) blocks is the order they've been written._
|
145
|
+
- run [`after_clone`](after_clone.md) callbacks
|
146
|
+
- __SAVE CLONED RECORD__
|
147
|
+
- run [`after_persist`](after_persist.md) callbacks
|
148
|
+
|
149
|
+
## Motivation & Alternatives
|
150
|
+
|
151
|
+
### Why did we decide to build our own cloning gem instead of using the existing solutions?
|
152
|
+
|
153
|
+
First, the existing solutions turned out not to be stable and flexible enough for us.
|
154
|
+
|
155
|
+
Secondly, they are Rails-only (or, more precisely, ActiveRecord-only).
|
156
|
+
|
157
|
+
Nevertheless, thanks to [amoeba](https://github.com/amoeba-rb/amoeba) and [deep_cloneable](https://github.com/moiristo/deep_cloneable) for inspiration.
|
158
|
+
|
159
|
+
For ActiveRecord we support amoeba-like [in-model configuration](active_record.md) and you can add missing DSL declarations yourself [easily](customization.md).
|
160
|
+
|
161
|
+
We also provide an ability to specify cloning [configuration in-place](inline_configuration.md) like `deep_clonable` does.
|
162
|
+
|
163
|
+
So, we took the best of these too and brought to the outside-of-Rails world.
|
164
|
+
|
165
|
+
### Why build a gem to clone models at all?
|
166
|
+
|
167
|
+
That's a good question. Of course, you can write plain old Ruby services do handle the cloning logic. But for complex models hierarchies, this approach has major disadvantages: high code complexity and lack of re-usability.
|
168
|
+
|
169
|
+
The things become even worse when you deal with STI models and different cloning contexts.
|
170
|
+
|
171
|
+
That's why we decided to build a specific cloning tool.
|
data/docs/implicit_cloner.md
CHANGED
data/docs/include_association.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: include_association
|
3
|
-
title: Include Association
|
4
|
-
---
|
1
|
+
# Include Association
|
5
2
|
|
6
3
|
Use this declaration to clone model's associations:
|
7
4
|
|
@@ -27,8 +24,8 @@ include_association name, scope, options
|
|
27
24
|
|
28
25
|
Adapter |1:1 |*:1 | 1:M | M:M |
|
29
26
|
------------------------------------------|------------|------------|-------------|-------------------------|
|
30
|
-
[Active Record](
|
31
|
-
[Sequel](
|
27
|
+
[Active Record](active_record) | has_one | belongs_to | has_many | has_and_belongs_to|
|
28
|
+
[Sequel](sequel) | one_to_one | - | one_to_many | many_to_many |
|
32
29
|
|
33
30
|
## Scope
|
34
31
|
|
data/docs/index.html
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Document</title>
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
+
<meta name="description" content="Description">
|
8
|
+
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
9
|
+
<link rel="stylesheet" href="assets/vue.css">
|
10
|
+
<link rel="stylesheet" href="assets/styles.css">
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<div id="app"></div>
|
14
|
+
<script>
|
15
|
+
window.$docsify = {
|
16
|
+
name: 'Clowne',
|
17
|
+
repo: 'https://github.com/clowne-rb/clowne',
|
18
|
+
loadSidebar: true,
|
19
|
+
subMaxLevel: 2,
|
20
|
+
auto2top: true,
|
21
|
+
search: {
|
22
|
+
namespace: 'clowne'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
</script>
|
26
|
+
<script src="assets/docsify.min.js"></script>
|
27
|
+
<script src="assets/prism-ruby.min.js"></script>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/docs/init_as.md
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
|
2
|
-
id: init_as
|
3
|
-
title: Initialize Cloning Target
|
4
|
-
sidebar_label: Init As
|
5
|
-
---
|
1
|
+
# Initialize Cloning Target
|
6
2
|
|
7
3
|
You can override the default Clowne method which generates a _plain_ copy of a source object.
|
8
4
|
By default, Clowne initiates the cloned record using a `#dup` method.
|
@@ -27,12 +23,12 @@ class UserCloner < Clowne::Cloner
|
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
30
|
-
jack = User.find_by(email:
|
26
|
+
jack = User.find_by(email: "jack@evl.ms")
|
31
27
|
# => <#User id: 1, ...>
|
32
|
-
jack.create_profile(name:
|
28
|
+
jack.create_profile(name: "Jack")
|
33
29
|
# => <#Profile id: 1, name: 'Jack', ...>
|
34
30
|
|
35
|
-
john = User.find_by(email:
|
31
|
+
john = User.find_by(email: "john@evl.ms")
|
36
32
|
# => <#User id: 2, ...>
|
37
33
|
|
38
34
|
# we want to clone Jack's profile to John's user,
|
data/docs/nullify.md
CHANGED
data/docs/operation.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: operation
|
3
|
-
title: Operation
|
4
|
-
---
|
1
|
+
# Operation
|
5
2
|
|
6
3
|
Since version 1.0 Clowne has been returning specific result object instead of a raw cloned object. It has allowed unifying interface between adapters and has opened an opportunity to implement new features. We call this object `Operation`.
|
7
4
|
|
@@ -18,7 +15,7 @@ class UserCloner < Clowne::Cloner
|
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
|
-
user = User.create(email:
|
18
|
+
user = User.create(email: "evl.ms")
|
22
19
|
# => <#User id: 1, email: 'evl.ms', ...>
|
23
20
|
|
24
21
|
operation = UserCloner.call(user)
|
@@ -37,7 +34,7 @@ operation.to_record
|
|
37
34
|
# Call only after_persist callbacks:
|
38
35
|
user2 = operation.to_record
|
39
36
|
# => <#User id: 2, email: 'evl-2.ms', ...>
|
40
|
-
user2.update_attributes(email:
|
37
|
+
user2.update_attributes(email: "admin@example.com")
|
41
38
|
# => <#User id: 2, email: 'admin@example.com' ...>
|
42
39
|
operation.run_after_persist
|
43
40
|
# => <#User id: 2, email: 'evl-2.ms', ...>
|
data/docs/parameters.md
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
id: parameters
|
3
|
-
title: Parameters
|
4
|
-
---
|
1
|
+
# Parameters
|
5
2
|
|
6
3
|
Clowne provides parameters for make your cloning logic more flexible. You can see their using in [`include_association`](include_association.md#scope) and [`finalize`](finalize.md) documentation pages.
|
7
4
|
|
@@ -16,7 +13,7 @@ class UserCloner < Clowne::Cloner
|
|
16
13
|
end
|
17
14
|
end
|
18
15
|
|
19
|
-
operation = UserCloner.call(user, state: :draft, email:
|
16
|
+
operation = UserCloner.call(user, state: :draft, email: "cloned@example.com")
|
20
17
|
cloned = operation.to_record
|
21
18
|
cloned.email
|
22
19
|
# => 'cloned@example.com'
|
@@ -42,7 +39,7 @@ end
|
|
42
39
|
|
43
40
|
## Nested Parameters
|
44
41
|
|
45
|
-
Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/
|
42
|
+
Also we implemented control over the parameters for cloning associations (you can read more [here](https://github.com/clowne-rb/clowne/issues/15)).
|
46
43
|
|
47
44
|
Let's explain what the difference:
|
48
45
|
|
@@ -78,7 +75,7 @@ class UserCloner < Clowne::Cloner
|
|
78
75
|
include_association :profile, params: Proc.new do |params, user|
|
79
76
|
{
|
80
77
|
name: params[:profile][:name],
|
81
|
-
email: user.email
|
78
|
+
email: user.email,
|
82
79
|
}
|
83
80
|
end
|
84
81
|
end
|
@@ -93,7 +90,7 @@ end
|
|
93
90
|
# Execute:
|
94
91
|
|
95
92
|
def get_profile_jsonb(user, trait)
|
96
|
-
params = {
|
93
|
+
params = {profile: {name: "John", surname: "Cena"}}
|
97
94
|
cloned = UserCloner.call(user, traits: trait, **params).to_record
|
98
95
|
cloned.profile.jsonb_field
|
99
96
|
end
|