tiny_decorator 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 556129b98566d8008a5debe82b1b482fdca17b671a7f5216efe52b6cb3c133a9
4
- data.tar.gz: bbe8d99109236b1af9f6b0627e7efb5de0bb609a02a3113fbb940812da8e71eb
3
+ metadata.gz: 364b7fcece5d8c7d52133df5b8acf3ccb5b79ea275e47ed9f5955d297b25f163
4
+ data.tar.gz: bec109f60ccecf10b5cb18182188f2e4a435bf00af57327226155129654fdcb0
5
5
  SHA512:
6
- metadata.gz: a7c87eab3b528b9cbbdc1cb63550a2d64bcfff06bfe15bc5af5db13a3abdce1153ec09615c96bbeb56cb06c2cc4c73c8e0a52f7841134d9c231585e5ee97b8aa
7
- data.tar.gz: 8decc6dd24638f49b3c71a4165a9d92353689943b4ee48accf6514188256d35ed7d9195f445da1c219b66f277dd2d24f1652dd2b651770d1e9d4ef9d8054ffc4
6
+ metadata.gz: 316ed45920375aba4766fbd74454b857e17d1425c60c12f559b5d0978cf26d64561ce5d4fbc6f8bbdb77f4452a7cc574a5c12f3e881c598377389beb1dfa51b5
7
+ data.tar.gz: 9944fb695a7e58467e522bcc9bccff267ccda41cfe0167504eb8e4952df0f55ec26f867ba2ec5a6630ef3fa51a9bdd49948efec4138694b5ced25aa97c73c6cb
data/Gemfile CHANGED
@@ -4,3 +4,8 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in tiny_decorator.gemspec
6
6
  gemspec
7
+
8
+ gem 'pry-byebug'
9
+ gem 'benchmark-ips'
10
+ gem 'draper'
11
+ gem 'ruby-prof'
@@ -1,13 +1,77 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tiny_decorator (0.1.0)
4
+ tiny_decorator (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ actionpack (6.0.2.1)
10
+ actionview (= 6.0.2.1)
11
+ activesupport (= 6.0.2.1)
12
+ rack (~> 2.0, >= 2.0.8)
13
+ rack-test (>= 0.6.3)
14
+ rails-dom-testing (~> 2.0)
15
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
16
+ actionview (6.0.2.1)
17
+ activesupport (= 6.0.2.1)
18
+ builder (~> 3.1)
19
+ erubi (~> 1.4)
20
+ rails-dom-testing (~> 2.0)
21
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
22
+ activemodel (6.0.2.1)
23
+ activesupport (= 6.0.2.1)
24
+ activemodel-serializers-xml (1.0.2)
25
+ activemodel (> 5.x)
26
+ activesupport (> 5.x)
27
+ builder (~> 3.1)
28
+ activesupport (6.0.2.1)
29
+ concurrent-ruby (~> 1.0, >= 1.0.2)
30
+ i18n (>= 0.7, < 2)
31
+ minitest (~> 5.1)
32
+ tzinfo (~> 1.1)
33
+ zeitwerk (~> 2.2)
34
+ benchmark-ips (2.7.2)
35
+ builder (3.2.4)
36
+ byebug (11.1.1)
37
+ coderay (1.1.2)
38
+ concurrent-ruby (1.1.6)
39
+ crass (1.0.6)
9
40
  diff-lcs (1.3)
10
- rake (10.5.0)
41
+ draper (4.0.0)
42
+ actionpack (>= 5.0)
43
+ activemodel (>= 5.0)
44
+ activemodel-serializers-xml (>= 1.0)
45
+ activesupport (>= 5.0)
46
+ request_store (>= 1.0)
47
+ erubi (1.9.0)
48
+ i18n (1.8.2)
49
+ concurrent-ruby (~> 1.0)
50
+ loofah (2.4.0)
51
+ crass (~> 1.0.2)
52
+ nokogiri (>= 1.5.9)
53
+ method_source (0.9.2)
54
+ mini_portile2 (2.4.0)
55
+ minitest (5.14.0)
56
+ nokogiri (1.10.9)
57
+ mini_portile2 (~> 2.4.0)
58
+ pry (0.12.2)
59
+ coderay (~> 1.1.0)
60
+ method_source (~> 0.9.0)
61
+ pry-byebug (3.8.0)
62
+ byebug (~> 11.0)
63
+ pry (~> 0.10)
64
+ rack (2.2.2)
65
+ rack-test (1.1.0)
66
+ rack (>= 1.0, < 3)
67
+ rails-dom-testing (2.0.3)
68
+ activesupport (>= 4.2.0)
69
+ nokogiri (>= 1.6)
70
+ rails-html-sanitizer (1.3.0)
71
+ loofah (~> 2.3)
72
+ rake (13.0.1)
73
+ request_store (1.5.0)
74
+ rack (>= 1.4)
11
75
  rspec (3.8.0)
12
76
  rspec-core (~> 3.8.0)
13
77
  rspec-expectations (~> 3.8.0)
@@ -21,15 +85,24 @@ GEM
21
85
  diff-lcs (>= 1.2.0, < 2.0)
22
86
  rspec-support (~> 3.8.0)
23
87
  rspec-support (3.8.0)
88
+ ruby-prof (1.3.1)
89
+ thread_safe (0.3.6)
90
+ tzinfo (1.2.6)
91
+ thread_safe (~> 0.1)
92
+ zeitwerk (2.3.0)
24
93
 
25
94
  PLATFORMS
26
95
  ruby
27
96
 
28
97
  DEPENDENCIES
98
+ benchmark-ips
29
99
  bundler (~> 1.16)
30
- rake (~> 10.0)
100
+ draper
101
+ pry-byebug
102
+ rake (>= 12.3.3)
31
103
  rspec (~> 3.0)
104
+ ruby-prof
32
105
  tiny_decorator!
33
106
 
34
107
  BUNDLED WITH
35
- 1.17.1
108
+ 1.17.3
data/README.md CHANGED
@@ -63,6 +63,21 @@ NewDecorator.decorate_collection(models, context)
63
63
 
64
64
  ```
65
65
 
66
+ ### Preload
67
+
68
+ In case rails' eager loading doesn't fit, or we don't use. `preload` could be used to avoid N+1.
69
+
70
+ ```ruby
71
+ preload :count_all, ->(all_records, context, preloaded) { Group(all_records).count }
72
+ # all_records - all the records to decorate
73
+ # context - The context passed to collection decorating,
74
+ # because preload run once before all decratings, this is the only cotext we have at this time
75
+ # preloaded - all preloaded before. For perfomrance, it's mutable, please handle with care
76
+ ```
77
+
78
+ Then each single decorator could access through `preload[:count_all]`.
79
+ Note: `preload` will be run once before decorating all records, compare to context will be ran for each record.
80
+
66
81
  ## Development
67
82
 
68
83
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,6 +1,7 @@
1
1
  module TinyDecorator
2
2
  #
3
3
  # Passing only decorator name, object will be decorated
4
+ #
4
5
  # ```
5
6
  # extend TinyDecorator::CompositeDecorator
6
7
  # decorated_by :default, 'DefaultDecorator'
@@ -33,18 +34,37 @@ module TinyDecorator
33
34
  # The decorator is a sub class of TinyDecorator::BaseDelegator (or draper decorator, but not recommend)
34
35
  # TinyDecorator::BaseDelegator will answer the question which attributes are decorated.
35
36
  #
37
+ # In case we don't have eager loading or rails' eager loading doesn't match,
38
+ # preload block could be used to manually load data to avoid N+1.
39
+ # Then access through 3rd param of decorator
40
+ #
41
+ # ```ruby
42
+ # preload :count_all, ->(all_records, context, preloaded) { Group(all_records).count }
43
+ # ```
44
+ # all_records - all the records to decorate
45
+ # context - The context passed to collection decorating,
46
+ # because preload run once before all decratings, this is the only cotext we have at this time
47
+ # preloaded - all preloaded before. For perfomrance, it's mutable, please handle with care
48
+ #
36
49
  module CompositeDecorator
37
50
  # Decorate collection of objects, each object is decorate by `#decorate`
38
- # TODO: [AV] It's greate if with activerecord relationshop, we defer decorate until data retrieved.
51
+ # TODO: [AV] It's greate if with activerecord relationship, we defer decorate until data retrieved.
39
52
  # Using `map` will make data retrieval executes immediately
40
53
  def decorate_collection(records, context = {})
54
+ if instance_variable_get(:@_preloaders)
55
+ preloaded = {}
56
+ instance_variable_get(:@_preloaders).each do |preloader, execute_block|
57
+ preloaded[preloader] = execute_block.call(records, context, preloaded)
58
+ end
59
+ end
60
+
41
61
  Array(records).map do |record|
42
- decorate(record, context)
62
+ decorate(record, context, preloaded)
43
63
  end
44
64
  end
45
65
 
46
66
  # Decorate an object by defined `#decorated_by`
47
- def decorate(record, context = {})
67
+ def decorate(record, context = {}, preloaded = {})
48
68
  if instance_variable_get(:@_contexts)
49
69
  context = context.merge(instance_variable_get(:@_contexts).inject({}) do |carry, (context_name, context_block)|
50
70
  context[context_name] = context_block.call(record, context)
@@ -53,14 +73,14 @@ module TinyDecorator
53
73
  end)
54
74
  end
55
75
 
56
- instance_variable_get(:@decorators).inject(record) do |carry, (name, value)|
76
+ instance_variable_get(:@_decorators).inject(record) do |carry, (name, value)|
57
77
  decorator = decorator_resolver(name, value, record, context)
58
78
  if decorator
59
79
  carry = begin
60
80
  const_get(decorator, false)
61
81
  rescue NameError
62
82
  Object.const_get(decorator, false)
63
- end.decorate(carry, context)
83
+ end.decorate(carry, context, preloaded)
64
84
  end
65
85
 
66
86
  carry
@@ -69,18 +89,31 @@ module TinyDecorator
69
89
 
70
90
  private
71
91
 
92
+ # decorated_by
72
93
  def decorated_by(decorate_name, class_name, condition_block = nil)
73
- decorators = instance_variable_get(:@decorators) || {}
94
+ decorators = instance_variable_get(:@_decorators) || {}
74
95
  decorators[decorate_name] = [class_name, condition_block]
75
- instance_variable_set(:@decorators, decorators)
96
+ instance_variable_set(:@_decorators, decorators)
76
97
  end
77
98
 
99
+ # set_context
78
100
  def set_context(context_name, context_block)
79
101
  _contexts = instance_variable_get(:@_contexts) || {}
80
102
  _contexts[context_name] = context_block
81
103
  instance_variable_set(:@_contexts, _contexts)
82
104
  end
83
105
 
106
+ # preload
107
+ # Similar to context. but run once on whole collection
108
+ # preload preload_name, ->(records, preloaded) do
109
+ # Relation.where(id: records.map(&:relation_id).compact.uniq)
110
+ # end
111
+ def preload(preloader, preloader_block)
112
+ _preloaders = instance_variable_get(:@_preloaders) || {}
113
+ _preloaders[preloader] = preloader_block
114
+ instance_variable_set(:@_preloaders, _preloaders)
115
+ end
116
+
84
117
  # Resolve decorator class from #decorated_by definition
85
118
  # if 1st param is a block, evaluate it as decorator name
86
119
  # if 1st param isn't a block
@@ -0,0 +1,79 @@
1
+ module TinyDecorator
2
+ module Delegatable
3
+ RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
4
+ else elsif END end ensure false for if in module next nil not or redo rescue retry
5
+ return self super then true undef unless until when while yield)
6
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
7
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS).freeze
8
+
9
+ unless defined?(delegate)
10
+ # =========================================================
11
+ # File activesupport/lib/active_support/core_ext/module/delegation.rb, line 154
12
+ # In case we don't use rails, include their well known `delegate`
13
+ # I have no idea of how to write it better
14
+ # =========================================================
15
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
16
+ unless to
17
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
18
+ end
19
+
20
+ if prefix == true && /^[^a-z_]/.match?(to)
21
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
22
+ end
23
+
24
+ method_prefix = if prefix
25
+ "#{prefix == true ? to : prefix}_"
26
+ else
27
+ ""
28
+ end
29
+
30
+ location = caller_locations(1, 1).first
31
+ file, line = location.path, location.lineno
32
+
33
+ to = to.to_s
34
+ to = "self.#{to}" if TinyDecorator::Delegatable::DELEGATION_RESERVED_METHOD_NAMES.include?(to)
35
+
36
+ methods.map do |method|
37
+ # Attribute writer methods only accept one argument. Makes sure []=
38
+ # methods still accept two arguments.
39
+ definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
40
+
41
+ # The following generated method calls the target exactly once, storing
42
+ # the returned value in a dummy variable.
43
+ #
44
+ # Reason is twofold: On one hand doing less calls is in general better.
45
+ # On the other hand it could be that the target has side-effects,
46
+ # whereas conceptually, from the user point of view, the delegator should
47
+ # be doing one call.
48
+ if allow_nil
49
+ method_def = [
50
+ "def #{method_prefix}#{method}(#{definition})",
51
+ "_ = #{to}",
52
+ "if !_.nil? || nil.respond_to?(:#{method})",
53
+ " _.#{method}(#{definition})",
54
+ "end",
55
+ "end"
56
+ ].join ";"
57
+ else
58
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
59
+
60
+ method_def = [
61
+ "def #{method_prefix}#{method}(#{definition})",
62
+ " _ = #{to}",
63
+ " _.#{method}(#{definition})",
64
+ "rescue NoMethodError => e",
65
+ " if _.nil? && e.name == :#{method}",
66
+ " #{exception}",
67
+ " else",
68
+ " raise",
69
+ " end",
70
+ "end"
71
+ ].join ";"
72
+ end
73
+
74
+ module_eval(method_def, file, line)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,3 +1,5 @@
1
+ require 'tiny_decorator/delegatable'
2
+
1
3
  module TinyDecorator
2
4
  # Single decorator. Inherite, then define methods to use.
3
5
  # This base decorator delegates all. To limit delegation to source object.
@@ -19,11 +21,12 @@ module TinyDecorator
19
21
  # Initialize, use `.decorate` or `.new` are the same
20
22
  # @param delegatee [Object] source object to decorate
21
23
  # @param context [Hash] context as hash, could read within decorator scope by `context[]`
22
- def initialize(delegatee, context = {})
24
+ def initialize(delegatee, context = {}, preloaded = {})
23
25
  @context = context
26
+ @preloaded = preloaded
24
27
  __setobj__(delegatee)
25
28
  end
26
- attr_reader :context
29
+ attr_reader :context, :preloaded
27
30
 
28
31
  class << self
29
32
  # Decorate a collection, collection must extend Enum behavior
@@ -36,6 +39,8 @@ module TinyDecorator
36
39
  end
37
40
 
38
41
  alias decorate new
42
+
43
+ include TinyDecorator::Delegatable
39
44
  end
40
45
  end
41
46
  end
@@ -1,3 +1,3 @@
1
1
  module TinyDecorator
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -34,6 +34,6 @@ Gem::Specification.new do |spec|
34
34
  spec.require_paths = ["lib"]
35
35
 
36
36
  spec.add_development_dependency "bundler", "~> 1.16"
37
- spec.add_development_dependency "rake", "~> 10.0"
37
+ spec.add_development_dependency "rake", ">= 12.3.3"
38
38
  spec.add_development_dependency "rspec", "~> 3.0"
39
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiny_decorator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Vo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-01 00:00:00.000000000 Z
11
+ date: 2020-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 12.3.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,7 @@ files:
75
75
  - bin/setup
76
76
  - lib/tiny_decorator.rb
77
77
  - lib/tiny_decorator/composite_decorator.rb
78
+ - lib/tiny_decorator/delegatable.rb
78
79
  - lib/tiny_decorator/single_decorator.rb
79
80
  - lib/tiny_decorator/version.rb
80
81
  - tiny_decorator.gemspec
@@ -98,7 +99,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
99
  - !ruby/object:Gem::Version
99
100
  version: '0'
100
101
  requirements: []
101
- rubygems_version: 3.0.2
102
+ rubyforge_project:
103
+ rubygems_version: 2.7.7
102
104
  signing_key:
103
105
  specification_version: 4
104
106
  summary: A simple decorator untility module