clowne 0.1.0.pre1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +7 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +17 -0
  6. data/.travis.yml +15 -2
  7. data/CHANGELOG.md +9 -2
  8. data/Gemfile +1 -0
  9. data/README.md +25 -381
  10. data/clowne.gemspec +1 -0
  11. data/docs/.rubocop.yml +12 -0
  12. data/docs/active_record.md +36 -0
  13. data/docs/alternatives.md +26 -0
  14. data/docs/architecture.md +141 -0
  15. data/docs/basic_example.md +66 -0
  16. data/docs/configuration.md +29 -0
  17. data/docs/customization.md +64 -0
  18. data/docs/exclude_association.md +63 -0
  19. data/docs/execution_order.md +14 -0
  20. data/docs/finalize.md +35 -0
  21. data/docs/implicit_cloner.md +36 -0
  22. data/docs/include_association.md +119 -0
  23. data/docs/init_as.md +36 -0
  24. data/docs/inline_configuration.md +38 -0
  25. data/docs/installation.md +16 -0
  26. data/docs/nullify.md +37 -0
  27. data/docs/sequel.md +56 -0
  28. data/docs/supported_adapters.md +13 -0
  29. data/docs/traits.md +28 -0
  30. data/docs/web/.gitignore +11 -0
  31. data/docs/web/core/Footer.js +92 -0
  32. data/docs/web/i18n/en.json +134 -0
  33. data/docs/web/package.json +14 -0
  34. data/docs/web/pages/en/help.js +50 -0
  35. data/docs/web/pages/en/index.js +231 -0
  36. data/docs/web/pages/en/users.js +47 -0
  37. data/docs/web/sidebars.json +30 -0
  38. data/docs/web/siteConfig.js +44 -0
  39. data/docs/web/static/css/custom.css +229 -0
  40. data/docs/web/static/fonts/FiraCode-Medium.woff +0 -0
  41. data/docs/web/static/fonts/FiraCode-Regular.woff +0 -0
  42. data/docs/web/static/fonts/StemText.woff +0 -0
  43. data/docs/web/static/fonts/StemTextBold.woff +0 -0
  44. data/docs/web/static/img/favicon/favicon.ico +0 -0
  45. data/docs/web/yarn.lock +1741 -0
  46. data/gemfiles/activerecord42.gemfile +1 -0
  47. data/gemfiles/jruby.gemfile +2 -0
  48. data/gemfiles/railsmaster.gemfile +1 -0
  49. data/lib/clowne.rb +3 -1
  50. data/lib/clowne/adapters/active_record.rb +3 -12
  51. data/lib/clowne/adapters/active_record/association.rb +1 -1
  52. data/lib/clowne/adapters/active_record/associations/base.rb +8 -48
  53. data/lib/clowne/adapters/active_record/associations/has_one.rb +8 -1
  54. data/lib/clowne/adapters/active_record/associations/noop.rb +4 -1
  55. data/lib/clowne/adapters/active_record/dsl.rb +33 -0
  56. data/lib/clowne/adapters/base.rb +13 -6
  57. data/lib/clowne/adapters/base/association.rb +69 -0
  58. data/lib/clowne/adapters/base/finalize.rb +1 -1
  59. data/lib/clowne/adapters/base/init_as.rb +21 -0
  60. data/lib/clowne/adapters/registry.rb +5 -11
  61. data/lib/clowne/adapters/sequel.rb +25 -0
  62. data/lib/clowne/adapters/sequel/association.rb +47 -0
  63. data/lib/clowne/adapters/sequel/associations.rb +26 -0
  64. data/lib/clowne/adapters/sequel/associations/base.rb +23 -0
  65. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +19 -0
  66. data/lib/clowne/adapters/sequel/associations/noop.rb +16 -0
  67. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +23 -0
  68. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +23 -0
  69. data/lib/clowne/adapters/sequel/copier.rb +23 -0
  70. data/lib/clowne/adapters/sequel/record_wrapper.rb +59 -0
  71. data/lib/clowne/cloner.rb +6 -4
  72. data/lib/clowne/declarations.rb +1 -0
  73. data/lib/clowne/declarations/exclude_association.rb +0 -5
  74. data/lib/clowne/declarations/include_association.rb +0 -2
  75. data/lib/clowne/declarations/init_as.rb +20 -0
  76. data/lib/clowne/declarations/trait.rb +2 -0
  77. data/lib/clowne/ext/orm_ext.rb +21 -0
  78. data/lib/clowne/ext/string_constantize.rb +2 -2
  79. data/lib/clowne/planner.rb +11 -4
  80. data/lib/clowne/version.rb +1 -1
  81. metadata +70 -4
@@ -0,0 +1,14 @@
1
+ ---
2
+ id: execution_order
3
+ title: Execution Order
4
+ ---
5
+
6
+ The order of cloning actions depends on the adapter (i.e., could be customized).
7
+
8
+ All built-in adapters have the same order:
9
+ - init clone (see [`init_as`](init_as.md))
10
+ - clone associations
11
+ - nullify attributes
12
+ - run [`finalize`](finalize.md) blocks
13
+
14
+ The order of [`finalize`](finalize.md) blocks is the order they've been written.
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: finalize
3
+ title: Finalization
4
+ sidebar_label: Finalize
5
+ ---
6
+
7
+ To apply custom transformations to the cloned record, you can use the `finalize` declaration:
8
+
9
+ ```ruby
10
+ class UserCloner < Clowne::Cloner
11
+ finalize do |_source, record, _params|
12
+ record.name = 'This is copy!'
13
+ end
14
+
15
+ trait :change_email do
16
+ finalize do |_source, record, params|
17
+ record.email = params[:email]
18
+ end
19
+ end
20
+ end
21
+
22
+ clone = UserCloner.call(user)
23
+ clone.name
24
+ # => 'This is copy!'
25
+ clone.email == 'clone@example.com'
26
+ # => false
27
+
28
+ clone2 = UserCloner.call(user, traits: :change_email)
29
+ clone2.name
30
+ # => 'This is copy!'
31
+ clone2.email
32
+ # => 'clone@example.com'
33
+ ```
34
+
35
+ Finalization blocks are called at the end of the [cloning process](execution_order.md).
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: implicit_cloner
3
+ title: Implicit Cloner
4
+ ---
5
+
6
+ When [cloning associations](include_association.md) Clowne tries to infer an appropriate cloner class for the records (unless `clone_with` specified).
7
+
8
+ It relies on the naming convention: `MyModel` -> `MyModelCloner`.
9
+
10
+ Consider an example:
11
+
12
+ ```ruby
13
+ class User < ActiveRecord::Base
14
+ has_one :profile
15
+ end
16
+
17
+ class UserCloner < Clowne::Cloner
18
+ include_association :profile
19
+ end
20
+
21
+ class ProfileCloner < Clowne::Cloner
22
+ finalize do |source, record|
23
+ record.name = "Clone of #{source.name}"
24
+ end
25
+ end
26
+
27
+ user = User.last
28
+ user.profile.name
29
+ #=> "Bimbo"
30
+
31
+ cloned = UserCloner.call(user)
32
+ cloned.profile.name
33
+ # => "Clone of Bimbo"
34
+ ```
35
+
36
+ **NOTE:** when using [in-model cloner](active_record.md) for ActiveRecord it is used by default.
@@ -0,0 +1,119 @@
1
+ ---
2
+ id: include_association
3
+ title: Include Association
4
+ ---
5
+
6
+ Use this declaration to clone model's associations:
7
+
8
+ ```ruby
9
+ class User < ActiveRecord::Base
10
+ has_one :profile
11
+ end
12
+
13
+ class UserCloner < Clowne::Cloner
14
+ include_association :profile
15
+ end
16
+ ```
17
+
18
+ Looks pretty simple, right? But that's not all we may offer you! :)
19
+
20
+ The declaration supports additional arguments:
21
+
22
+ ```ruby
23
+ include_association name, scope, options
24
+ ```
25
+
26
+ ## Scope
27
+
28
+ Scope can be a:
29
+ - `Symbol` - named scope
30
+ - `Proc` - custom scope (supports parameters).
31
+
32
+ Example:
33
+
34
+ ```ruby
35
+ class User < ActiveRecord::Base
36
+ has_many :accounts
37
+ has_many :posts
38
+ end
39
+
40
+ class Account < ActiveRecord::Base
41
+ scope :active, -> { where(active: true) }
42
+ end
43
+
44
+ class Post < ActiveRecord::Base
45
+ # t.string :status
46
+ end
47
+
48
+ class UserCloner < Clowne::Cloner
49
+ include_association :accounts, :active
50
+ include_association :posts, ->(params) { where(state: params[:status]) if params[:status] }
51
+ end
52
+
53
+ # Clone only draft posts
54
+ UserCloner.call(user, status: :draft)
55
+ # => <#User...
56
+ ```
57
+
58
+ ## Options
59
+
60
+ The following options are available:
61
+ - `:clone_with` - use custom cloner\*
62
+ - `:traits` - define special traits.
63
+
64
+ \* **NOTE:** the same cloner class would be used for **all children**
65
+
66
+ Example:
67
+
68
+ ```ruby
69
+ class User < ActiveRecord::Base
70
+ has_many :posts
71
+ end
72
+
73
+ class Post < ActiveRecord::Base
74
+ # t.string :title
75
+ has_many :tags
76
+ end
77
+ ```
78
+
79
+ ```ruby
80
+ class PostSpecialCloner < Clowne::Cloner
81
+ nullify :title
82
+
83
+ trait :with_tags do
84
+ include_association :tags
85
+ end
86
+ end
87
+
88
+ class UserCloner < Clowne::Cloner
89
+ adapter :active_record
90
+
91
+ include_association :posts, clone_with: PostSpecialCloner
92
+ # or clone user's posts with tags!
93
+ # include_association :posts, clone_with: PostSpecialCloner, traits: :with_tags
94
+ end
95
+
96
+ UserCloner.call(user)
97
+ # => <#User...
98
+ ```
99
+
100
+ **NOTE**: if custom cloner is not defined, Clowne tries to infer the [implicit cloner](implicit_cloner.md).
101
+
102
+ ## Include multiple associations
103
+
104
+ You can include multiple associations at once too:
105
+
106
+ ```ruby
107
+ class User < ActiveRecord::Base
108
+ has_many :accounts
109
+ has_many :posts
110
+ end
111
+
112
+ class UserCloner < Clowne::Cloner
113
+ adapter :active_record
114
+
115
+ include_associations :accounts, :posts
116
+ end
117
+ ```
118
+
119
+ **NOTE:** in that case, it's not possible to provide custom scopes and options.
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: init_as
3
+ title: Initialize Cloning Target
4
+ sidebar_label: Init As
5
+ ---
6
+
7
+ You can override the default Clowne method which generates a _plain_ copy of a source object.
8
+ By default, Clowne initiates the cloned record using a `#dup` method.
9
+
10
+ For example, Cloners could be used not only to generate _fresh_ new models but to apply some transformations to the existing record:
11
+
12
+
13
+ ```ruby
14
+ class User < ApplicationRecord
15
+ has_one :profile
16
+ has_many :posts
17
+ end
18
+
19
+ class UserCloner < Clowne::Cloner
20
+ adapter :active_record
21
+
22
+ include_association :profile
23
+
24
+ trait :copy_settings do
25
+ # Use a `target` for all the actions
26
+ init_as { |_source, target:| target }
27
+ end
28
+ end
29
+
30
+ jack = User.find_by(email: 'jack@evl.ms')
31
+ john = User.find_by(email: 'john@evl.ms')
32
+
33
+ # we want to clone Jack's profile settings to another user,
34
+ # without creating a new one
35
+ UserCloner.call(jack, traits: :copy_settings, target: john)
36
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: inline_configuration
3
+ title: Inline Configuration
4
+ ---
5
+
6
+ You can also enhance the cloner configuration inline (i.e., add declarations dynamically):
7
+
8
+ ```ruby
9
+ cloned = UserCloner.call(User.last) do
10
+ exclude_association :profile
11
+
12
+ finalize do |source, record|
13
+ record.email = "clone_of_#{source.email}"
14
+ end
15
+ end
16
+
17
+ cloned.email
18
+ # => "clone_of_john@example.com"
19
+
20
+ # associations:
21
+ cloned.posts.size == User.last.posts.size
22
+ # => true
23
+ cloned.profile
24
+ # => nil
25
+ ```
26
+
27
+ Inline enhancement doesn't affect the _global_ configuration so that you can use it without any fear.
28
+
29
+ Thus it's also possible to clone objects without any cloner classes at all by using `Clowne::Cloner`:
30
+
31
+ ```ruby
32
+ cloned = Clowne::Cloner.call(user) do
33
+ # anything you want!
34
+ end
35
+
36
+ cloned
37
+ # => <#User..
38
+ ```
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: installation
3
+ title: Installation
4
+ ---
5
+
6
+ To install Clowne with RubyGems:
7
+
8
+ ```ruby
9
+ gem install clowne
10
+ ```
11
+
12
+ Or add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'clowne'
16
+ ```
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: nullify
3
+ title: Nullify Attributes
4
+ sidebar_label: Nullify
5
+ ---
6
+
7
+ To set a bunch of attributes to `nil` you can use the `nullify` declaration:
8
+
9
+ ```ruby
10
+ class User < ActiveRecord::Base
11
+ # t.string :name
12
+ # t.string :surname
13
+ # t.string :email
14
+ end
15
+
16
+ class UserCloner < Clowne::Cloner
17
+ nullify :name, :email
18
+
19
+ trait :nullify_surname do
20
+ nullify :surname
21
+ end
22
+ end
23
+
24
+ clone = UserCloner.call(user)
25
+ clone.name.nil?
26
+ # => true
27
+ clone.email.nil?
28
+ # => true
29
+ clone.surname.nil?
30
+ # => false
31
+
32
+ clone2 = UserCloner.call(user, traits: :nullify_surname)
33
+ clone2.name.nil?
34
+ # => true
35
+ clone2.surname.nil?
36
+ # => true
37
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ id: sequel
3
+ title: Sequel
4
+ ---
5
+
6
+ Clowne uses Sequel [`NestedAttributes` plugin](http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html) for cloning source's associations and you need to configure it.
7
+
8
+ Also, Sequel target record wrapped into a special class for implementation full Clowne's behavior. You need to use method `to_model` for getting final cloned `Sequel::Model` object (or you can use `save` for saving the cloned object to DB).
9
+
10
+ Example:
11
+
12
+ ```ruby
13
+ class UserCloner < Clowne::Cloner
14
+ adapter :sequel
15
+
16
+ include_association :account
17
+ end
18
+
19
+ class User < Sequel::Model
20
+ # Configure NestedAttributes plugin
21
+ plugin :nested_attributes
22
+
23
+ one_to_one :account
24
+ nested_attributes :account
25
+ end
26
+ ```
27
+
28
+ and get cloned user
29
+
30
+ ```ruby
31
+ user = User.last
32
+ wrapper = UserCloner.call(user)
33
+ wrapper.class
34
+ # => Clowne::Adapters::Sequel::RecordWrapper
35
+ cloned_record = wrapper.to_model
36
+ cloned_record.class
37
+ # => User
38
+ cloned_record.new?
39
+ # => true
40
+ ```
41
+
42
+ or you can save it immediately
43
+
44
+ ```ruby
45
+ user = User.last
46
+ wrapper = UserCloner.call(user)
47
+ wrapper.class
48
+ # => Clowne::Adapters::Sequel::RecordWrapper
49
+ cloned_record = wrapper.save
50
+ cloned_record.class
51
+ # => User
52
+ cloned_record.new?
53
+ # => false
54
+ ```
55
+
56
+ If you try to clone associations without `NestedAttributes` plugin, Clowne will skip this declaration.
@@ -0,0 +1,13 @@
1
+ ---
2
+ id: supported_adapters
3
+ title: Supported Adapters
4
+ ---
5
+
6
+ Clowne supports the following ORM adapters (and associations):
7
+
8
+ Adapter |1:1 | 1:M | M:M |
9
+ ---------------------------------------------------|------------|-------------|-------------------------|
10
+ [:active_record](/clowne/docs/active_record.html) | has_one | has_many | has_and_belongs_to_many |
11
+ [:sequel](/clowne/docs/sequel.html) | one_to_one | one_to_many | many_to_many |
12
+
13
+ For more information see the corresponding adapter documentation.
@@ -0,0 +1,28 @@
1
+ ---
2
+ id: traits
3
+ title: Traits
4
+ ---
5
+
6
+ Traits allow you to group cloner declarations together and then apply them (like in [`factory_bot`](https://github.com/thoughtbot/factory_bot)):
7
+
8
+ ```ruby
9
+ class UserCloner < Clowne::Cloner
10
+ trait :with_posts do
11
+ include_association :posts
12
+ end
13
+
14
+ trait :with_profile do
15
+ include_association :profile
16
+ end
17
+
18
+ trait :nullify_name do
19
+ nullify :name
20
+ end
21
+ end
22
+
23
+ UserCloner.call(user, traits: %i[with_posts with_profile nullify_name])
24
+ # or
25
+ UserCloner.call(user, traits: :nullify_name)
26
+ # or
27
+ # ...
28
+ ```
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ .DS_Store
3
+ lib/core/metadata.js
4
+ lib/core/MetadataBlog.js
5
+ website/translated_docs
6
+ website/build/
7
+ website/yarn.lock
8
+ website/node_modules
9
+
10
+ website/i18n/*
11
+ !website/i18n/en.json