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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -51
  3. data/.travis.yml +11 -23
  4. data/Gemfile +9 -9
  5. data/README.md +8 -7
  6. data/Rakefile +3 -3
  7. data/clowne.gemspec +14 -8
  8. data/docs/.nojekyll +0 -0
  9. data/docs/.rubocop.yml +8 -2
  10. data/docs/CNAME +1 -0
  11. data/docs/README.md +131 -0
  12. data/docs/_sidebar.md +25 -0
  13. data/docs/active_record.md +2 -5
  14. data/docs/after_clone.md +3 -6
  15. data/docs/after_persist.md +7 -10
  16. data/docs/architecture.md +2 -5
  17. data/docs/assets/docsify.min.js +1 -0
  18. data/docs/assets/prism-ruby.min.js +1 -0
  19. data/docs/assets/styles.css +348 -0
  20. data/docs/assets/vue.css +1 -0
  21. data/docs/clone_mapper.md +2 -5
  22. data/docs/customization.md +1 -4
  23. data/docs/exclude_association.md +1 -4
  24. data/docs/finalize.md +4 -8
  25. data/docs/from_v02_to_v1.md +2 -10
  26. data/docs/getting_started.md +171 -0
  27. data/docs/implicit_cloner.md +1 -4
  28. data/docs/include_association.md +3 -6
  29. data/docs/index.html +29 -0
  30. data/docs/init_as.md +4 -8
  31. data/docs/inline_configuration.md +1 -4
  32. data/docs/nullify.md +1 -5
  33. data/docs/operation.md +3 -6
  34. data/docs/parameters.md +5 -8
  35. data/docs/sequel.md +1 -4
  36. data/docs/supported_adapters.md +3 -6
  37. data/docs/testing.md +18 -21
  38. data/docs/traits.md +1 -4
  39. data/gemfiles/activerecord42.gemfile +5 -5
  40. data/gemfiles/jruby.gemfile +6 -6
  41. data/gemfiles/railsmaster.gemfile +6 -6
  42. data/lib/clowne.rb +11 -11
  43. data/lib/clowne/adapters/active_record.rb +3 -3
  44. data/lib/clowne/adapters/active_record/associations.rb +7 -7
  45. data/lib/clowne/adapters/active_record/associations/base.rb +1 -1
  46. data/lib/clowne/adapters/active_record/associations/belongs_to.rb +1 -1
  47. data/lib/clowne/adapters/active_record/associations/has_one.rb +1 -1
  48. data/lib/clowne/adapters/active_record/resolvers/association.rb +1 -1
  49. data/lib/clowne/adapters/base.rb +6 -6
  50. data/lib/clowne/adapters/sequel.rb +7 -7
  51. data/lib/clowne/adapters/sequel/associations.rb +6 -6
  52. data/lib/clowne/adapters/sequel/associations/base.rb +2 -2
  53. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +4 -4
  54. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +1 -1
  55. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +1 -1
  56. data/lib/clowne/adapters/sequel/operation.rb +3 -2
  57. data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +1 -1
  58. data/lib/clowne/adapters/sequel/resolvers/association.rb +1 -1
  59. data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +1 -1
  60. data/lib/clowne/cloner.rb +8 -8
  61. data/lib/clowne/declarations.rb +15 -15
  62. data/lib/clowne/declarations/after_clone.rb +1 -1
  63. data/lib/clowne/declarations/after_persist.rb +1 -1
  64. data/lib/clowne/declarations/finalize.rb +1 -1
  65. data/lib/clowne/declarations/include_association.rb +1 -1
  66. data/lib/clowne/declarations/init_as.rb +1 -1
  67. data/lib/clowne/declarations/nullify.rb +1 -1
  68. data/lib/clowne/ext/orm_ext.rb +1 -1
  69. data/lib/clowne/ext/record_key.rb +1 -1
  70. data/lib/clowne/ext/string_constantize.rb +1 -1
  71. data/lib/clowne/ext/yield_self_then.rb +1 -1
  72. data/lib/clowne/planner.rb +1 -1
  73. data/lib/clowne/rspec.rb +3 -3
  74. data/lib/clowne/rspec/clone_association.rb +3 -3
  75. data/lib/clowne/rspec/clone_associations.rb +2 -2
  76. data/lib/clowne/rspec/helpers.rb +1 -1
  77. data/lib/clowne/utils/clone_mapper.rb +1 -1
  78. data/lib/clowne/utils/operation.rb +3 -3
  79. data/lib/clowne/utils/params.rb +1 -1
  80. data/lib/clowne/version.rb +1 -1
  81. metadata +50 -35
  82. data/docs/alternatives.md +0 -26
  83. data/docs/basic_example.md +0 -83
  84. data/docs/installation.md +0 -46
  85. data/docs/overview.md +0 -25
  86. data/docs/web/.gitignore +0 -11
  87. data/docs/web/README.md +0 -6
  88. data/docs/web/core/Footer.js +0 -88
  89. data/docs/web/i18n/en.json +0 -141
  90. data/docs/web/package.json +0 -14
  91. data/docs/web/pages/en/help.js +0 -50
  92. data/docs/web/pages/en/index.js +0 -231
  93. data/docs/web/pages/en/users.js +0 -47
  94. data/docs/web/sidebars.json +0 -38
  95. data/docs/web/siteConfig.js +0 -46
  96. data/docs/web/static/css/custom.css +0 -235
  97. data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
  98. data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
  99. data/docs/web/static/img/favicon/favicon.ico +0 -0
  100. data/docs/web/yarn.lock +0 -1741
@@ -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}
@@ -1,9 +1,6 @@
1
- ---
2
- id: clone_mapper
3
- title: Clone mapper
4
- ---
1
+ # Clone mapper
5
2
 
6
- _Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter._
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
 
@@ -1,7 +1,4 @@
1
- ---
2
- id: customization
3
- title: Customization
4
- ---
1
+ # Customization
5
2
 
6
3
  Clowne is built with extensibility in mind. You can create your own DSL commands and resolvers.
7
4
 
@@ -1,7 +1,4 @@
1
- ---
2
- id: exclude_association
3
- title: Exclude Association
4
- ---
1
+ # Exclude Association
5
2
 
6
3
  Clowne doesn't include any association by default and doesn't provide _magic_ `include_all` declaration (although you can [add one by yourself](customization.md)).
7
4
 
@@ -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 = 'This is copy!'
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 == 'clone@example.com'
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](execution_order.md).
31
+ Finalization blocks are called at the end of the [cloning process](getting_started?id=execution-order).
@@ -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
- _Notice: `after_persist` supported only with [`active_record`](active_record.md) adapter._
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.
@@ -1,7 +1,4 @@
1
- ---
2
- id: implicit_cloner
3
- title: Implicit Cloner
4
- ---
1
+ # Implicit Cloner
5
2
 
6
3
  When [cloning associations](include_association.md) Clowne tries to infer an appropriate cloner class for the records (unless `clone_with` specified).
7
4
 
@@ -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](https://clowne.evilmartians.io/clowne/docs/active_record.html) | has_one | belongs_to | has_many | has_and_belongs_to|
31
- [Sequel](https://clowne.evilmartians.io/clowne/docs/sequel.html) | one_to_one | - | one_to_many | many_to_many |
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
 
@@ -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>
@@ -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: 'jack@evl.ms')
26
+ jack = User.find_by(email: "jack@evl.ms")
31
27
  # => <#User id: 1, ...>
32
- jack.create_profile(name: 'Jack')
28
+ jack.create_profile(name: "Jack")
33
29
  # => <#Profile id: 1, name: 'Jack', ...>
34
30
 
35
- john = User.find_by(email: 'john@evl.ms')
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,
@@ -1,7 +1,4 @@
1
- ---
2
- id: inline_configuration
3
- title: Inline Configuration
4
- ---
1
+ # Inline Configuration
5
2
 
6
3
  You can also enhance the cloner configuration inline (i.e., add declarations dynamically):
7
4
 
@@ -1,8 +1,4 @@
1
- ---
2
- id: nullify
3
- title: Nullify Attributes
4
- sidebar_label: Nullify
5
- ---
1
+ # Nullify Attributes
6
2
 
7
3
  To set a bunch of attributes to `nil` you can use the `nullify` declaration:
8
4
 
@@ -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: 'evl.ms')
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: 'admin@example.com')
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', ...>
@@ -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: 'cloned@example.com')
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/palkan/clowne/issues/15)).
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 = { profile: { name: 'John', surname: 'Cena' } }
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