tint 0.0.2 → 0.0.3

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
  SHA1:
3
- metadata.gz: f7f4999d404212c1a6af7966791b29d5563deaaa
4
- data.tar.gz: b42c343d31b7ce2974679a7b37243e4337ff2971
3
+ metadata.gz: 9b646a5aca0a4609a2285d5add776d0b0269ce11
4
+ data.tar.gz: d613dd2da3a435acfa9fc7c3e2927e0da9428808
5
5
  SHA512:
6
- metadata.gz: 7868f81c19bae633ad1f7f560be0d93bc9f5ef84376f4533c376fdf8b67fb5e6eb2291dc24657918d573d8658bbdf1a6b10911902c0bc94363c6e912f16f3b79
7
- data.tar.gz: 7437f4f6567aaee8abbf250e40d94496eb7fdbe5ed976fb7b55ae4726fbf2a8830056a8212d60257d8cf1d3bf6508a53b2e76b17aca2e9ba68264c7e7a69a504
6
+ metadata.gz: d4ac9d15bc28cefbe96dd4c5b322cd4efe111878abe84c64e9dbb683b99e6ebf2eca68751e868640eed9033c982408c16c3da6f9fbda27f0edf8838f9c43f1ef
7
+ data.tar.gz: 6e21cdf0b19fd87cc5f381cf7e9b45344f7d58d283d9d832cd56c020ed5b5c8155f18066bdc55a261d4f3953f82512d76f097a761af267e562a73ad781e8b145
data/README.md CHANGED
@@ -90,6 +90,12 @@ By default, `decorates_association` uses the name of the association to guess th
90
90
  decorates_association :product, with: SpecialProductDecorator
91
91
  ```
92
92
 
93
+ You can decorate an association and make it available under a different name using the `as` option:
94
+
95
+ ```ruby
96
+ decorates_association :product, as: :sale_item
97
+ ```
98
+
93
99
  Multiple associations can be defined in the same statement using `decorates_associations`. Either, using interpolation to locate the correct decorator for each:
94
100
 
95
101
  ```ruby
@@ -1,4 +1,11 @@
1
+ require "tint/version"
2
+ require "tint/decorator"
3
+
1
4
  module Tint
5
+ class Decorator
6
+
7
+ end
8
+
2
9
  class << self
3
10
  attr_accessor :attribute_capitalization
4
11
 
@@ -13,6 +20,3 @@ module Tint
13
20
 
14
21
  @attribute_capitalization = :camel_case
15
22
  end
16
-
17
- require "tint/version"
18
- require "tint/decorator"
@@ -1,4 +1,5 @@
1
1
  require 'draper'
2
+ require 'deep_merge/rails_compat'
2
3
  require_relative 'json_conversion.rb'
3
4
 
4
5
  module Tint
@@ -18,7 +19,21 @@ module Tint
18
19
  end
19
20
 
20
21
  class << self
21
- attr_accessor :_attributes, :eager_loads
22
+ attr_accessor :_attributes, :parent_decorator, :parent_association
23
+
24
+ def eager_loads
25
+ {}
26
+ end
27
+
28
+ def eager_loads=(value)
29
+ singleton_class.class_eval do
30
+ remove_possible_method(:eager_loads)
31
+
32
+ define_method(:eager_loads){
33
+ value
34
+ }
35
+ end
36
+ end
22
37
 
23
38
  def attributes(*options)
24
39
  @_attributes ||= Set.new
@@ -36,23 +51,56 @@ module Tint
36
51
 
37
52
  def eager_load(*schema)
38
53
  @_attributes ||= Set.new
39
- @eager_loads ||= []
40
54
 
41
- schema.each do |schema_item|
42
- @eager_loads.push(schema_item)
43
- end
44
- end
55
+ new_eager_loads =
56
+ schema.inject({}) do |memo, schema_item|
57
+ if schema_item.kind_of?(Hash)
58
+ memo = memo.merge(schema_item)
59
+ else
60
+ memo[schema_item] = {}
61
+ end
62
+
63
+ memo
64
+ end
65
+
66
+ self.eager_loads = self.eager_loads.deeper_merge(new_eager_loads)
67
+ end
45
68
 
46
69
  def decorates_association(association_name, options = {})
47
70
  options[:with] ||= (association_name.to_s.camelize.singularize + 'Decorator').constantize
48
71
 
49
- super(association_name, options)
72
+ association_alias = options.delete(:as)
73
+
74
+ options[:context] ||= {}
75
+ options[:context][:parent_decorator] = self
76
+ options[:context][:parent_association] = association_name
77
+
78
+ ap "association_name:"
79
+ ap association_name
80
+
81
+ ap "options:"
82
+ ap options
83
+
84
+
85
+ if association_alias
86
+ options.assert_valid_keys(:with, :scope, :context)
87
+
88
+ define_method(association_alias) do
89
+ decorated_associations[association_alias] ||= Draper::DecoratedAssociation.new(self, association_name, options)
90
+ decorated_associations[association_alias].call
91
+ end
92
+
93
+ attributes(association_alias)
94
+ else
95
+ super(association_name, options)
96
+
97
+ attributes(association_name)
98
+ end
50
99
 
51
- attributes(association_name)
52
100
  association_eager_loads = options[:with].eager_loads
53
101
 
54
102
  if association_eager_loads.present?
55
- eager_load({ association_name => association_eager_loads})
103
+ eager_load({ association_name => association_eager_loads})
56
104
  else
57
105
  eager_load(association_name)
58
106
  end
@@ -68,12 +116,25 @@ module Tint
68
116
  end
69
117
 
70
118
  def decorate_collection(collection, options = {})
71
- collection_with_eager_loads =
72
- if collection.respond_to?(:includes) && eager_loads.present?
73
- collection.includes(*eager_loads)
74
- else
75
- collection
76
- end
119
+ ap "options:"
120
+ ap options
121
+
122
+ collection_with_eager_loads = collection
123
+
124
+ if options[:context] && options[:context][:parent_decorator] && options[:context][:parent_association]
125
+ @parent_decorator = options[:context][:parent_decorator]
126
+ @parent_association = options[:context][:parent_association]
127
+
128
+ if collection.respond_to?(:includes) &&
129
+ !eager_loads.blank? &&
130
+ !parent_eager_loads_include_own?
131
+
132
+ # ap "decorate_collection eager_loading eager_loads:"
133
+ # ap eager_loads
134
+
135
+ collection_with_eager_loads = collection.includes(*eager_loads)
136
+ end
137
+ end
77
138
 
78
139
  super(collection_with_eager_loads, options)
79
140
  end
@@ -81,18 +142,40 @@ module Tint
81
142
  def decorate(object, options = {})
82
143
  object_class = object.class
83
144
 
84
- unless already_eager_loaded_associations?(object)
85
- object =
86
- if responds_to_methods?(object_class, :includes, :find) && eager_loads.present?
87
- object_class.includes(*eager_loads).find(object.id)
88
- else
89
- object
90
- end
91
- end
145
+ # unless already_eager_loaded_associations?(object)
146
+ # object =
147
+ # if responds_to_methods?(object_class, :includes, :find) && eager_loads.present?
148
+ # object_class.includes(*eager_loads).find(object.id)
149
+ # else
150
+ # object
151
+ # end
152
+ # end
92
153
 
93
154
  super(object, options)
94
155
  end
95
156
 
157
+ def parent_eager_loads_include_own?(association = nil)
158
+ association_chain = [@parent_assocation]
159
+ association_chain.push(association) if association
160
+
161
+ eager_loads_include?(@parent_decorator.eager_loads, association_chain) ||
162
+ @parent_decorator.parent_eager_loads_include_own?(association_chain)
163
+ end
164
+
165
+ def eager_loads_include?(hash, key_list)
166
+ if key_list.length > 1
167
+ next_key = key_list.shift
168
+
169
+ if (next_value = hash[next_key])
170
+ eager_loads_include?(next_value, key_list)
171
+ else
172
+ next_value
173
+ end
174
+ else
175
+ hash[key_list.first]
176
+ end
177
+ end
178
+
96
179
  private
97
180
 
98
181
  def already_eager_loaded_associations?(object)
@@ -117,7 +200,9 @@ module Tint
117
200
 
118
201
  unless method_defined?(delegate_method)
119
202
  define_method(delegate_method) do
120
- object.send(delegate_method)
203
+ if object.respond_to?(delegate_method)
204
+ object.send(delegate_method)
205
+ end
121
206
  end
122
207
  end
123
208
  end
@@ -128,12 +213,14 @@ module Tint
128
213
  @_attributes.add(decorator_attribute)
129
214
 
130
215
  define_method(decorator_attribute) do
131
- object.send(object_method)
216
+ if object.respond_to?(object_method)
217
+ object.send(object_method)
218
+ end
132
219
  end
133
220
  end
134
221
  end
135
222
  end
136
223
 
137
-
138
224
  end
225
+
139
226
  end
@@ -45,6 +45,10 @@ module Tint
45
45
  end
46
46
 
47
47
  def attributes_for_json
48
+ attribute_list = self.class._attributes
49
+
50
+ return {} if attribute_list.blank?
51
+
48
52
  strategy =
49
53
  case Tint.attribute_capitalization
50
54
  when :camel_case
@@ -57,7 +61,7 @@ module Tint
57
61
  AttributeNameStrategy::Stringify
58
62
  end
59
63
 
60
- self.class._attributes.inject({}) do |memo, key_and_value|
64
+ attribute_list.inject({}) do |memo, key_and_value|
61
65
  key, _ = key_and_value
62
66
 
63
67
  unless (value = self.send(key)).nil?
@@ -1,3 +1,3 @@
1
1
  module Tint
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -16,17 +16,44 @@ RSpec.describe Tint::Decorator do
16
16
 
17
17
  let(:object) { object_class.new('one', 'two') }
18
18
 
19
+ context "when not attributes have been set" do
20
+ let(:decorator_class) do
21
+ Class.new(Tint::Decorator) do
22
+
23
+ end
24
+ end
25
+
26
+ it "returns an empty object", focus: true do
27
+ expect(subject).to eql({})
28
+ end
29
+ end
30
+
19
31
  describe "::attributes" do
32
+ context "when an broken reference is used" do
33
+ let(:decorator_class) do
34
+ Class.new(Tint::Decorator) do
35
+ attributes :broken_reference
36
+ end
37
+ end
38
+
39
+ it "does not include it in the object" do
40
+ expect(subject).to eql({})
41
+ end
42
+ end
43
+
20
44
  context "when only delegations are defined" do
21
45
  let(:decorator_class) do
22
46
  Class.new(Tint::Decorator) do
23
- attributes :attr1, :attr2
47
+ attributes :attr1
24
48
  end
25
49
  end
26
50
 
27
51
  it "delegates attributes of the same name to the object" do
28
52
  expect(subject['attr1']).to eql('one')
29
- expect(subject['attr2']).to eql('two')
53
+ end
54
+
55
+ it "does not include any attributes not mentioned" do
56
+ expect(subject['attr2']).to be_nil
30
57
  end
31
58
  end
32
59
 
@@ -106,7 +133,7 @@ RSpec.describe Tint::Decorator do
106
133
  end
107
134
  end
108
135
 
109
- context "when a with option is provided" do
136
+ context "when a :with option is provided" do
110
137
  let(:explicitly_referenced_decorator) do
111
138
  Class.new(Tint::Decorator) do
112
139
  attributes decorated1: :attr1
@@ -131,6 +158,33 @@ RSpec.describe Tint::Decorator do
131
158
  Object.send(:remove_const, 'ExplicitlyReferencedDecorator')
132
159
  end
133
160
  end
161
+
162
+ context "when a :as option is provided" do
163
+
164
+ let(:associated_decorator) do
165
+ Class.new(Tint::Decorator) do
166
+ attributes :attr1
167
+ end
168
+ end
169
+
170
+ let(:decorator_class) do
171
+ Class.new(Tint::Decorator) do
172
+ decorates_association :associated, as: 'related'
173
+ end
174
+ end
175
+
176
+ before(:each) do
177
+ Object.const_set('AssociatedDecorator', associated_decorator)
178
+ end
179
+
180
+ it "uses the specified decorator" do
181
+ expect(subject['related']['attr1']).to eql('value')
182
+ end
183
+
184
+ after(:each) do
185
+ Object.send(:remove_const, 'AssociatedDecorator')
186
+ end
187
+ end
134
188
  end
135
189
 
136
190
  describe "::eager_load" do
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "draper", "~> 2.1"
22
+ spec.add_dependency "deep_merge", "~> 1.0"
22
23
  spec.add_development_dependency "bundler", "~> 1.6"
23
24
  spec.add_development_dependency "rake", "~> 0"
24
25
  spec.add_development_dependency "rspec", "~> 0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleck Greenham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-03 00:00:00.000000000 Z
11
+ date: 2016-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: draper
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: deep_merge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement