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 +4 -4
- data/Gemfile +5 -0
- data/Gemfile.lock +77 -4
- data/README.md +15 -0
- data/lib/tiny_decorator/composite_decorator.rb +40 -7
- data/lib/tiny_decorator/delegatable.rb +79 -0
- data/lib/tiny_decorator/single_decorator.rb +7 -2
- data/lib/tiny_decorator/version.rb +1 -1
- data/tiny_decorator.gemspec +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 364b7fcece5d8c7d52133df5b8acf3ccb5b79ea275e47ed9f5955d297b25f163
|
4
|
+
data.tar.gz: bec109f60ccecf10b5cb18182188f2e4a435bf00af57327226155129654fdcb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 316ed45920375aba4766fbd74454b857e17d1425c60c12f559b5d0978cf26d64561ce5d4fbc6f8bbdb77f4452a7cc574a5c12f3e881c598377389beb1dfa51b5
|
7
|
+
data.tar.gz: 9944fb695a7e58467e522bcc9bccff267ccda41cfe0167504eb8e4952df0f55ec26f867ba2ec5a6630ef3fa51a9bdd49948efec4138694b5ced25aa97c73c6cb
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,77 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tiny_decorator (0.1.
|
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
|
-
|
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
|
-
|
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.
|
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
|
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(:@
|
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(:@
|
94
|
+
decorators = instance_variable_get(:@_decorators) || {}
|
74
95
|
decorators[decorate_name] = [class_name, condition_block]
|
75
|
-
instance_variable_set(:@
|
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
|
data/tiny_decorator.gemspec
CHANGED
@@ -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", "
|
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.
|
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:
|
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:
|
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:
|
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
|
-
|
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
|