clowne 0.2.0 → 1.0.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.travis.yml +6 -3
  4. data/CHANGELOG.md +14 -0
  5. data/Gemfile +1 -1
  6. data/README.md +30 -9
  7. data/clowne.gemspec +4 -3
  8. data/docs/active_record.md +2 -2
  9. data/docs/after_persist.md +80 -0
  10. data/docs/basic_example.md +22 -5
  11. data/docs/clone_mapper.md +62 -0
  12. data/docs/customization.md +2 -1
  13. data/docs/exclude_association.md +5 -4
  14. data/docs/finalize.md +2 -2
  15. data/docs/from_v02_to_v1.md +91 -0
  16. data/docs/implicit_cloner.md +1 -1
  17. data/docs/include_association.md +4 -4
  18. data/docs/init_as.md +10 -2
  19. data/docs/inline_configuration.md +4 -2
  20. data/docs/installation.md +31 -1
  21. data/docs/nullify.md +2 -2
  22. data/docs/operation.md +58 -0
  23. data/docs/overview.md +24 -0
  24. data/docs/parameters.md +5 -4
  25. data/docs/sequel.md +15 -18
  26. data/docs/supported_adapters.md +2 -2
  27. data/docs/testing.md +7 -5
  28. data/docs/web/README.md +6 -0
  29. data/docs/web/core/Footer.js +5 -9
  30. data/docs/web/i18n/en.json +8 -4
  31. data/docs/web/pages/en/index.js +1 -1
  32. data/docs/web/sidebars.json +10 -4
  33. data/docs/web/siteConfig.js +6 -4
  34. data/docs/web/static/css/custom.css +16 -10
  35. data/gemfiles/activerecord42.gemfile +3 -1
  36. data/gemfiles/jruby.gemfile +2 -0
  37. data/gemfiles/railsmaster.gemfile +2 -0
  38. data/lib/clowne.rb +3 -0
  39. data/lib/clowne/adapters/active_record.rb +2 -3
  40. data/lib/clowne/adapters/active_record/associations/base.rb +0 -4
  41. data/lib/clowne/adapters/active_record/associations/has_one.rb +2 -1
  42. data/lib/clowne/adapters/active_record/resolvers/association.rb +38 -0
  43. data/lib/clowne/adapters/base.rb +42 -43
  44. data/lib/clowne/adapters/base/association.rb +24 -15
  45. data/lib/clowne/adapters/registry.rb +49 -0
  46. data/lib/clowne/adapters/sequel.rb +10 -6
  47. data/lib/clowne/adapters/sequel/associations/base.rb +8 -4
  48. data/lib/clowne/adapters/sequel/associations/many_to_many.rb +6 -2
  49. data/lib/clowne/adapters/sequel/associations/one_to_many.rb +7 -2
  50. data/lib/clowne/adapters/sequel/associations/one_to_one.rb +7 -2
  51. data/lib/clowne/adapters/sequel/operation.rb +32 -0
  52. data/lib/clowne/adapters/sequel/record_wrapper.rb +0 -16
  53. data/lib/clowne/adapters/sequel/resolvers/after_persist.rb +22 -0
  54. data/lib/clowne/adapters/sequel/resolvers/association.rb +51 -0
  55. data/lib/clowne/adapters/sequel/specifications/after_persist_does_not_support.rb +15 -0
  56. data/lib/clowne/cloner.rb +27 -20
  57. data/lib/clowne/declarations.rb +2 -1
  58. data/lib/clowne/declarations/after_persist.rb +21 -0
  59. data/lib/clowne/declarations/finalize.rb +1 -0
  60. data/lib/clowne/declarations/include_association.rb +2 -1
  61. data/lib/clowne/declarations/init_as.rb +1 -0
  62. data/lib/clowne/declarations/nullify.rb +1 -0
  63. data/lib/clowne/declarations/trait.rb +1 -0
  64. data/lib/clowne/dsl.rb +9 -0
  65. data/lib/clowne/ext/lambda_as_proc.rb +1 -0
  66. data/lib/clowne/ext/record_key.rb +12 -0
  67. data/lib/clowne/ext/yield_self_then.rb +25 -0
  68. data/lib/clowne/planner.rb +6 -3
  69. data/lib/clowne/resolvers/after_persist.rb +18 -0
  70. data/lib/clowne/resolvers/finalize.rb +12 -0
  71. data/lib/clowne/resolvers/init_as.rb +13 -0
  72. data/lib/clowne/resolvers/nullify.rb +15 -0
  73. data/lib/clowne/rspec/helpers.rb +1 -0
  74. data/lib/clowne/utils/clone_mapper.rb +26 -0
  75. data/lib/clowne/utils/operation.rb +83 -0
  76. data/lib/clowne/utils/options.rb +39 -0
  77. data/lib/clowne/utils/params.rb +64 -0
  78. data/lib/clowne/utils/plan.rb +90 -0
  79. data/lib/clowne/version.rb +1 -1
  80. metadata +44 -18
  81. data/docs/configuration.md +0 -29
  82. data/docs/execution_order.md +0 -14
  83. data/docs/web/static/fonts/StemText.woff +0 -0
  84. data/docs/web/static/fonts/StemTextBold.woff +0 -0
  85. data/lib/clowne/adapters/active_record/association.rb +0 -34
  86. data/lib/clowne/adapters/base/finalize.rb +0 -19
  87. data/lib/clowne/adapters/base/init_as.rb +0 -21
  88. data/lib/clowne/adapters/base/nullify.rb +0 -19
  89. data/lib/clowne/adapters/sequel/association.rb +0 -47
  90. data/lib/clowne/params.rb +0 -62
  91. data/lib/clowne/plan.rb +0 -83
@@ -82,7 +82,7 @@ class HomeSplash extends React.Component {
82
82
  <div className="inner">
83
83
  <ProjectTitle />
84
84
  <PromoSection>
85
- <Button href="/clowne/docs/installation.html">Getting Started</Button>
85
+ <Button href={docUrl("installation.html")}>Getting Started</Button>
86
86
  </PromoSection>
87
87
  </div>
88
88
  </SplashContainer>
@@ -3,29 +3,35 @@
3
3
  "Getting Started": [
4
4
  "installation",
5
5
  "basic_example",
6
- "configuration",
6
+ "overview",
7
7
  "alternatives"
8
8
  ],
9
9
  "API": [
10
+ "operation",
10
11
  "include_association",
11
12
  "exclude_association",
12
13
  "nullify",
13
14
  "finalize",
15
+ "after_persist",
14
16
  "init_as",
15
17
  "traits",
16
- "parameters",
17
- "execution_order"
18
+ "parameters"
18
19
  ],
19
20
  "Adapters": [
20
21
  "supported_adapters",
21
22
  "active_record",
22
23
  "sequel"
23
24
  ],
24
- "Features": [
25
+ "Advanced Options": [
25
26
  "implicit_cloner",
26
27
  "inline_configuration",
28
+ "clone_mapper",
27
29
  "architecture",
30
+ "testing",
28
31
  "customization"
32
+ ],
33
+ "Upgrade Notes": [
34
+ "from_v02_to_v10"
29
35
  ]
30
36
  }
31
37
  }
@@ -8,8 +8,9 @@
8
8
  const siteConfig = {
9
9
  title: 'Clowne' /* title for your website */,
10
10
  tagline: 'A flexible gem for cloning your models',
11
- url: 'https://github.com/palkan/clowne' /* your website url */,
12
- baseUrl: '/clowne/' /* base url for your project */,
11
+ url: 'http://clowne.evilmartians.io' /* your website url */,
12
+ cname: 'clowne.evilmartians.io',
13
+ baseUrl: '/' /* base url for your project */,
13
14
  customDocsPath: '../docs',
14
15
  projectName: 'clowne',
15
16
  headerLinks: [
@@ -22,7 +23,7 @@ const siteConfig = {
22
23
  favicon: 'img/favicon/favicon.ico',
23
24
  /* colors for website */
24
25
  colors: {
25
- primaryColor: '#111111', // '#ff5e5e'
26
+ primaryColor: '#9FA628', // '#ff5e5e'
26
27
  secondaryColor: '#e3e3e3',
27
28
  },
28
29
  // This copyright info is used in /core/Footer.js and blog rss/atom feeds.
@@ -33,12 +34,13 @@ const siteConfig = {
33
34
  // organizationName: 'deltice', // or set an env variable ORGANIZATION_NAME
34
35
  highlight: {
35
36
  // Highlight.js theme to use for syntax highlighting in code blocks
36
- theme: 'ascetic',
37
+ theme: 'atom-one-light',
37
38
  },
38
39
  scripts: ['https://buttons.github.io/buttons.js'],
39
40
  // You may provide arbitrary config keys to be used as needed by your template.
40
41
  repoUrl: 'https://github.com/palkan/clowne',
41
42
  gemUrl: 'https://rubygems.org/gems/clowne',
43
+ gaTrackingId: 'UA-104346673-2',
42
44
  };
43
45
 
44
46
  module.exports = siteConfig;
@@ -4,13 +4,13 @@
4
4
  font-weight: 400;
5
5
  font-style: normal;
6
6
  font-family: "Stem Text";
7
- src: url("../fonts/StemText.woff") format("woff");
7
+ src: url("//cdn.evilmartians.com/front/fonts/subset-StemText-Regular.woff") format("woff");
8
8
  }
9
9
  @font-face {
10
10
  font-weight: 700;
11
11
  font-style: normal;
12
12
  font-family: "Stem Text";
13
- src: url("../fonts/StemTextBold.woff") format("woff");
13
+ src: url("//cdn.evilmartians.com/front/fonts/subset-StemText-Bold.woff") format("woff");
14
14
  }
15
15
 
16
16
  @font-face {
@@ -49,16 +49,15 @@ footer .sitemap div {
49
49
  }
50
50
 
51
51
  .fixedHeaderContainer a {
52
- color: #111; /* #ff5e5e; */
52
+ color: #9FA628;
53
53
  }
54
54
 
55
55
  header h2 {
56
- color: #111; /* #ff5e5e; */
56
+ color: #9FA628;
57
57
  text-transform: uppercase;
58
58
  }
59
59
 
60
60
  .container .wrapper h2 {
61
- color: #111; /* #ff5e5e; */
62
61
  font-weight: bold;
63
62
  }
64
63
 
@@ -66,6 +65,14 @@ header h2 {
66
65
  text-decoration: underline;
67
66
  }
68
67
 
68
+ .projectTitle {
69
+ color: #111;
70
+ }
71
+
72
+ .projectTitleName {
73
+ color: #9FA628;
74
+ }
75
+
69
76
  .mainContainer .wrapper a:hover {
70
77
  text-decoration: none;
71
78
  }
@@ -79,7 +86,7 @@ small {
79
86
  }
80
87
 
81
88
  .navigationSlider .slidingNav ul li a {
82
- color: #111; /* #ff5e5e; */
89
+ color: #9FA628;
83
90
  }
84
91
 
85
92
  nav.toc .toggleNav .navGroup.navGroupActive {
@@ -91,9 +98,8 @@ nav.toc .toggleNav .navGroup.navGroupActive {
91
98
  margin-bottom: 30px;
92
99
  }
93
100
 
94
- /* Make ascetic theme comments a little bit darker */
95
- .hljs-comment, .hljs-quote, .hljs-meta, .hljs-deletion {
96
- color: #a7a7a7;
101
+ .hljs-doctag {
102
+ color: #a0a1a7;
97
103
  }
98
104
 
99
105
  @keyframes humanoids-blink{
@@ -226,4 +232,4 @@ footer h5{
226
232
  .mainContainer {
227
233
  background: none;
228
234
  border-top: 1px solid #e3e3e3;
229
- }
235
+ }
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gem 'activerecord', '~> 4.2'
4
6
  gem 'sequel', '>= 5.0'
5
- gem 'sqlite3'
7
+ gem 'sqlite3', '~> 1.3.6'
6
8
 
7
9
  gemspec path: '..'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gem 'activerecord-jdbcsqlite3-adapter', '~> 50.0'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gem 'arel', github: 'rails/arel'
@@ -9,11 +9,13 @@ require 'clowne/adapters/base'
9
9
  # Declarative models cloning
10
10
  module Clowne
11
11
  # List of built-in adapters
12
+ # rubocop:disable AlignHash
12
13
  ADAPTERS = {
13
14
  base: 'Base',
14
15
  active_record: 'ActiveRecord',
15
16
  sequel: 'Sequel'
16
17
  }.freeze
18
+ # rubocop:enable AlignHash
17
19
 
18
20
  class << self
19
21
  attr_reader :default_adapter, :raise_on_override
@@ -29,6 +31,7 @@ module Clowne
29
31
  elsif adapter.is_a?(Symbol)
30
32
  adapter_class = ADAPTERS[adapter]
31
33
  raise "Unknown adapter: #{adapter}" if adapter_class.nil?
34
+
32
35
  Clowne::Adapters.const_get(adapter_class).new
33
36
  else
34
37
  adapter
@@ -5,8 +5,7 @@ require 'clowne/ext/orm_ext'
5
5
  module Clowne
6
6
  module Adapters
7
7
  # Cloning adapter for ActiveRecord
8
- class ActiveRecord < Base
9
- end
8
+ class ActiveRecord < Base; end
10
9
  end
11
10
  end
12
11
 
@@ -15,4 +14,4 @@ ActiveSupport.on_load(:active_record) do
15
14
  end
16
15
 
17
16
  require 'clowne/adapters/active_record/associations'
18
- require 'clowne/adapters/active_record/association'
17
+ require 'clowne/adapters/active_record/resolvers/association'
@@ -9,10 +9,6 @@ module Clowne
9
9
  class Base < Base::Association
10
10
  private
11
11
 
12
- def clone_record(record)
13
- record.dup
14
- end
15
-
16
12
  def init_scope
17
13
  association
18
14
  end
@@ -9,7 +9,8 @@ module Clowne
9
9
  def call(record)
10
10
  child = association
11
11
  return record unless child
12
- unless scope.nil?
12
+
13
+ unless declaration.scope.nil?
13
14
  warn(
14
15
  '[Clowne] Has one association does not support scopes ' \
15
16
  "(#{@association_name} for #{@source.class})"
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clowne
4
+ module Adapters # :nodoc: all
5
+ class ActiveRecord
6
+ module Resolvers
7
+ class UnknownAssociation < StandardError; end
8
+
9
+ class Association
10
+ class << self
11
+ # rubocop: disable Metrics/ParameterLists
12
+ def call(source, record, declaration, adapter:, params:, **_options)
13
+ reflection = source.class.reflections[declaration.name.to_s]
14
+
15
+ if reflection.nil?
16
+ raise UnknownAssociation,
17
+ "Association #{declaration.name} couldn't be found for #{source.class}"
18
+ end
19
+
20
+ cloner_class = Associations.cloner_for(reflection)
21
+
22
+ cloner_class.new(reflection, source, declaration, adapter, params).call(record)
23
+
24
+ record
25
+ end
26
+ # rubocop: enable Metrics/ParameterLists
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Clowne::Adapters::ActiveRecord.register_resolver(
35
+ :association,
36
+ Clowne::Adapters::ActiveRecord::Resolvers::Association,
37
+ before: :nullify
38
+ )
@@ -2,46 +2,34 @@
2
2
 
3
3
  require 'clowne/adapters/registry'
4
4
 
5
+ require 'clowne/resolvers/init_as'
6
+ require 'clowne/resolvers/nullify'
7
+ require 'clowne/resolvers/finalize'
8
+ require 'clowne/resolvers/after_persist'
9
+
5
10
  module Clowne
6
11
  module Adapters
7
12
  # ORM-independant adapter (just calls #dup).
8
13
  # Works with nullify/finalize.
9
14
  class Base
10
- class << self
11
- attr_reader :registry
12
-
13
- def inherited(subclass)
14
- # Duplicate registry
15
- subclass.registry = registry.dup
16
- end
17
-
18
- def resolver_for(type)
19
- registry.mapping[type] || raise("Uknown resolver #{type} for #{self}")
20
- end
15
+ include Clowne::Adapters::Registry::Container
21
16
 
22
- def register_resolver(type, resolver, after: nil, before: nil, prepend: nil)
23
- registry.mapping[type] = resolver
24
-
25
- if prepend
26
- registry.unshift type
27
- elsif after
28
- registry.insert_after after, type
29
- elsif before
30
- registry.insert_before before, type
31
- else
32
- registry.append type
17
+ class << self
18
+ # Duplicate record and remember record <-> clone relationship in operation
19
+ # Cab be overrided in special adapter
20
+ # +record+:: Instance of record (ActiveRecord or Sequel)
21
+ def dup_record(record)
22
+ record.dup.tap do |clone|
23
+ operation = operation_class.current
24
+ operation.add_mapping(record, clone)
33
25
  end
34
26
  end
35
27
 
36
- protected
37
-
38
- attr_writer :registry
39
- end
40
-
41
- self.registry = Registry.new
42
-
43
- def registry
44
- self.class.registry
28
+ # Operation class which using for cloning
29
+ # Cab be overrided in special adapter
30
+ def operation_class
31
+ Clowne::Utils::Operation
32
+ end
45
33
  end
46
34
 
47
35
  # Using a plan make full duplicate of record
@@ -50,19 +38,13 @@ module Clowne
50
38
  # +params+:: Custom params hash
51
39
  def clone(source, plan, params: {})
52
40
  declarations = plan.declarations
53
- declarations.inject(init_record(dup_source(source))) do |record, (type, declaration)|
41
+ init_record = init_record(self.class.dup_record(source))
42
+
43
+ declarations.inject(init_record) do |record, (type, declaration)|
54
44
  resolver_for(type).call(source, record, declaration, params: params, adapter: self)
55
45
  end
56
46
  end
57
47
 
58
- def resolver_for(type)
59
- self.class.resolver_for(type)
60
- end
61
-
62
- def dup_source(source)
63
- source.dup
64
- end
65
-
66
48
  def init_record(record)
67
49
  # Override in custom adapters
68
50
  record
@@ -71,6 +53,23 @@ module Clowne
71
53
  end
72
54
  end
73
55
 
74
- require 'clowne/adapters/base/init_as'
75
- require 'clowne/adapters/base/nullify'
76
- require 'clowne/adapters/base/finalize'
56
+ Clowne::Adapters::Base.register_resolver(
57
+ :init_as,
58
+ Clowne::Resolvers::InitAs,
59
+ prepend: true
60
+ )
61
+
62
+ Clowne::Adapters::Base.register_resolver(
63
+ :nullify,
64
+ Clowne::Resolvers::Nullify
65
+ )
66
+
67
+ Clowne::Adapters::Base.register_resolver(
68
+ :finalize, Clowne::Resolvers::Finalize,
69
+ after: :nullify
70
+ )
71
+
72
+ Clowne::Adapters::Base.register_resolver(
73
+ :after_persist, Clowne::Resolvers::AfterPersist,
74
+ after: :finalize
75
+ )
@@ -9,16 +9,15 @@ module Clowne
9
9
  # +source+:: Instance of cloned object (ex: User.new(posts: posts))
10
10
  # +declaration+:: = Relation description
11
11
  # (ex: Clowne::Declarations::IncludeAssociation.new(:posts))
12
+ # +adapter+:: Clowne adapter
12
13
  # +params+:: = Instance of Hash
13
- def initialize(reflection, source, declaration, params)
14
+ def initialize(reflection, source, declaration, adapter, params)
14
15
  @source = source
15
- @scope = declaration.scope
16
- @clone_with = declaration.clone_with
16
+ @declaration = declaration
17
+ @adapter = adapter
17
18
  @params = params
18
19
  @association_name = declaration.name.to_s
19
20
  @reflection = reflection
20
- @cloner_options = declaration.params_proxy.permit(params: params, parent: source)
21
- @cloner_options.merge!(traits: declaration.traits) if declaration.traits
22
21
  end
23
22
 
24
23
  def call(_record)
@@ -31,24 +30,24 @@ module Clowne
31
30
 
32
31
  def clone_one(child)
33
32
  cloner = cloner_for(child)
34
- cloner ? cloner.call(child, cloner_options) : clone_record(child)
33
+ cloner ? cloner.call(child, cloner_options) : dup_record(child)
35
34
  end
36
35
 
37
36
  def with_scope
38
- base_scope = init_scope
37
+ scope = declaration.scope
39
38
  if scope.is_a?(Symbol)
40
- base_scope.__send__(scope)
39
+ init_scope.__send__(scope)
41
40
  elsif scope.is_a?(Proc)
42
- base_scope.instance_exec(params, &scope) || base_scope
41
+ init_scope.instance_exec(params, &scope) || init_scope
43
42
  else
44
- base_scope
43
+ init_scope
45
44
  end.to_a
46
45
  end
47
46
 
48
47
  private
49
48
 
50
- def clone_record(_record)
51
- raise NotImplementedError
49
+ def dup_record(record)
50
+ adapter.class.dup_record(record)
52
51
  end
53
52
 
54
53
  def init_scope
@@ -56,13 +55,23 @@ module Clowne
56
55
  end
57
56
 
58
57
  def cloner_for(child)
59
- return clone_with if clone_with
58
+ return declaration.clone_with if declaration.clone_with
60
59
 
61
60
  return child.class.cloner_class if child.class.respond_to?(:cloner_class)
62
61
  end
63
62
 
64
- attr_reader :source, :scope, :clone_with, :params, :association_name,
65
- :reflection, :cloner_options
63
+ def cloner_options
64
+ return @_cloner_options if defined?(@_cloner_options)
65
+
66
+ @_cloner_options = declaration.params_proxy.permit(
67
+ params: params, parent: source
68
+ ).tap do |options|
69
+ options[:adapter] = adapter
70
+ options.merge!(traits: declaration.traits) if declaration.traits
71
+ end
72
+ end
73
+
74
+ attr_reader :source, :declaration, :adapter, :params, :association_name, :reflection
66
75
  end
67
76
  end
68
77
  end