clowne 0.1.0.pre1 → 0.1.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 (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