clowne 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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