tiny_decorator 0.1.0 → 0.1.1

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