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 +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
|