tint 0.0.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 +7 -0
- data/.gitignore +22 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +204 -0
- data/Rakefile +2 -0
- data/lib/tint.rb +18 -0
- data/lib/tint/decorator.rb +139 -0
- data/lib/tint/json_conversion.rb +71 -0
- data/lib/tint/version.rb +3 -0
- data/spec/support/active_record_mock.rb +5 -0
- data/spec/tint/decorator_spec.rb +158 -0
- data/tint.gemspec +25 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1354ca3ff0a496a512ab36ec8513968d975a3e2f
|
4
|
+
data.tar.gz: a5dad281f2b83296fa3d7ea158e1821d63aaf66e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a048c65f0003c91bef770a00aaa9e145501ae8306832d30b1c9aa4dfad91f2c5e2193a4d4b3aa29e1e5dd7dfef503c2c242b16b02eb4d35cecf91bde4bc2aa55
|
7
|
+
data.tar.gz: cc74836e050c7657a236eb74f095368fb1c577eb3ab0843e26017f5a9c148c41c5b0e3fc5b89ae369bc221067674b131c3bbba50743977b2e75849e29db1f367
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
spec/tmp
|
16
|
+
spec/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Aleck Greenham
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Tint
|
2
|
+
|
3
|
+
Easily define object decorators for JSON APIs using simple declarative syntax
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'tint'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install tint
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
You can use Tint by creating a decorator class that inherits from `Tint::Decorator`
|
22
|
+
|
23
|
+
## Defining attributes
|
24
|
+
|
25
|
+
To include methods and attributes available on the decorated object, simply list them using `attributes`.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# decorators/user_decorator.rb
|
29
|
+
class UserDecorator < Tint::Decorator
|
30
|
+
attributes :username, :first_name, :last_name
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
You can map attributes to different names on the decorator by providing a hash as the final argument
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# decorators/user_decorator.rb
|
38
|
+
class UserDecorator < Tint::Decorator
|
39
|
+
attributes :username, :first_name, last_name: :surname # object.surname will be available as ['last_name']
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Defining custom methods
|
44
|
+
|
45
|
+
Tint will use a decorator instance method in preference to one defined on the decorated object, so it is possible to customise how a particular attribute appears. The original definition of the attribute is available via the `object` instance variable.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class ProductDecorator < Tint::Decorator
|
49
|
+
attributes :id, :description, :price
|
50
|
+
|
51
|
+
def price
|
52
|
+
"$" + object.price
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
It's also possible to define methods that are not available on the decorated object at all.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class ProductDecorator < Tint::Decorator
|
61
|
+
attributes :id, :description, :on_sale
|
62
|
+
|
63
|
+
def on_sale
|
64
|
+
SaleItems.include?(object)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## Defining associations
|
70
|
+
|
71
|
+
The `decorates_association` method is used for declaring associations and delegating them to other decorators.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# decorators/product_decorator.rb
|
75
|
+
class ProductDecorator < Tint::Decorator
|
76
|
+
attributes :id, :description
|
77
|
+
end
|
78
|
+
|
79
|
+
# decorators/user_decorator.rb
|
80
|
+
class UserDecorator < Tint::Decorator
|
81
|
+
attributes :username
|
82
|
+
|
83
|
+
decorates_association :products
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
By default, `decorates_association` uses the name of the association to guess the decorator it should use. In this case it will use `ProductDecorator`, but if you wished to render with `SpecialProductDecorator`, the `with` option may be used:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
decorates_association :product, with: SpecialProductDecorator
|
91
|
+
```
|
92
|
+
|
93
|
+
Multiple associations can be defined in the same statement using `decorates_associations`. Either, using interpolation to locate the correct decorator for each:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
decorates_associations :product, :address
|
97
|
+
```
|
98
|
+
|
99
|
+
Or using the same decorator (in this case, `AddressDecorator`):
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
decorates_associations :previous_address, :current_address, with: AddressDecorator
|
103
|
+
```
|
104
|
+
|
105
|
+
## Eager loading associations
|
106
|
+
|
107
|
+
When you declare a new association using `decorates_association` or `decorates_associations`, Tint automatically eager loads the associations when the decorator is rendered as JSON and automatically prevents many N+1 queries. It does this by maintaining a list `eager_loads` which is available on all decorators.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class UserDecorator < Tint::Decorator
|
111
|
+
attributes :username
|
112
|
+
|
113
|
+
decorates_association :products
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
UserDecorator.eager_loads # [:products]
|
118
|
+
```
|
119
|
+
|
120
|
+
If you need to manually add to the list of associations which are eager loaded for any reason, you can do so using the `eager_load` method
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class UserDecorator < Tint::Decorator
|
124
|
+
attributes :username
|
125
|
+
|
126
|
+
decorates_association :products
|
127
|
+
|
128
|
+
eager_load :addresses
|
129
|
+
end
|
130
|
+
|
131
|
+
UserDecorator.eager_loads # [:products, :addresses]
|
132
|
+
```
|
133
|
+
|
134
|
+
## Decorating a single instance
|
135
|
+
|
136
|
+
Tint maintains the interface defined by Draper for decorating objects. To decorate a single object, use the `decorate` method.
|
137
|
+
|
138
|
+
Using the `UserDecorator` defined above:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
# controllers/users_controller.rb
|
142
|
+
|
143
|
+
class UsersController < ApplicationController
|
144
|
+
|
145
|
+
def show
|
146
|
+
@user = User.find(params[:id]) #<User username: "john_doe", first_name: "John", surname: "Doe">
|
147
|
+
|
148
|
+
render json: UserDecorator.decorate(@user) # { username: "john_doe", firstName: "John", lastName: "Doe" }
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
`decorate` also accepts an optional has of options. For more information about the supported options, see the [Draper documentation](https://github.com/drapergem/draper#adding-context).
|
155
|
+
|
156
|
+
|
157
|
+
## Decorating a collection
|
158
|
+
|
159
|
+
The `decorate_collection` method is used for decorating an instance of ActiveRecord::Relation (or any class that implements the same interface). It accepts all of the same options as the `decorate` method.
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# controllers/users_controller.rb
|
163
|
+
|
164
|
+
class UsersController < ApplicationController
|
165
|
+
|
166
|
+
def index
|
167
|
+
@users = User.all
|
168
|
+
|
169
|
+
render json: UserDecorator.decorate_collection(@users) # [ { username: "john_doe", firstName: "John", lastName: "Doe" }, ... ]
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
## Configuration
|
176
|
+
|
177
|
+
By default, Tint camelizes attribute names, however it's possible to configure Tint to use any of the following capitalization conventions:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
Tint.configuration do |config|
|
181
|
+
# attribute_naMe123 ==> attributeNaMe123 (Default)
|
182
|
+
# config.attribute_capitalization = :camel_case
|
183
|
+
|
184
|
+
# attribute_naMe123 ==> attribute_na_me123
|
185
|
+
# config.attribute_capitalization = :snake_case
|
186
|
+
|
187
|
+
# attribute_naMe123 ==> attribute-naMe123
|
188
|
+
# config.attribute_capitalization = :kebab_case
|
189
|
+
|
190
|
+
# Converts symbols to strings
|
191
|
+
# config.attribute_capitalization = :none
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
## Running the test suite
|
196
|
+
|
197
|
+
The test suite may be run simply using
|
198
|
+
|
199
|
+
rspec
|
200
|
+
|
201
|
+
## Contributions
|
202
|
+
|
203
|
+
Tint remains in its infancy and all pull requests, issues and feedback are welcome and appreciated.
|
204
|
+
|
data/Rakefile
ADDED
data/lib/tint.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Tint
|
2
|
+
class << self
|
3
|
+
attr_accessor :camelize_attribute_names
|
4
|
+
|
5
|
+
def configuration
|
6
|
+
if block_given?
|
7
|
+
yield(Tint)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :config :configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
@attribute_capitalization = :camel_case
|
15
|
+
end
|
16
|
+
|
17
|
+
require "tint/version"
|
18
|
+
require "tint/decorator"
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'draper'
|
2
|
+
require_relative 'json_conversion.rb'
|
3
|
+
|
4
|
+
module Tint
|
5
|
+
class Decorator < Draper::Decorator
|
6
|
+
include JsonConversion
|
7
|
+
|
8
|
+
def initialize(object, options = {})
|
9
|
+
super(object, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_names
|
13
|
+
object.class.column_names
|
14
|
+
end
|
15
|
+
|
16
|
+
def persisted?
|
17
|
+
object.persisted?
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :_attributes, :eager_loads
|
22
|
+
|
23
|
+
def attributes(*options)
|
24
|
+
@_attributes ||= Set.new
|
25
|
+
|
26
|
+
return unless options && options.any?
|
27
|
+
|
28
|
+
mapped_attrs = options.extract_options!
|
29
|
+
|
30
|
+
link_mappings_to_object(mapped_attrs)
|
31
|
+
|
32
|
+
delegated_attrs = options
|
33
|
+
|
34
|
+
link_delegations_to_object(delegated_attrs)
|
35
|
+
end
|
36
|
+
|
37
|
+
def eager_load(*schema)
|
38
|
+
@_attributes ||= Set.new
|
39
|
+
@eager_loads ||= []
|
40
|
+
|
41
|
+
schema.each do |schema_item|
|
42
|
+
@eager_loads.push(schema_item)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def decorates_association(association_name, options = {})
|
47
|
+
options[:with] ||= (association_name.to_s.camelize.singularize + 'Decorator').constantize
|
48
|
+
|
49
|
+
super(association_name, options)
|
50
|
+
|
51
|
+
attributes(association_name)
|
52
|
+
association_eager_loads = options[:with].eager_loads
|
53
|
+
|
54
|
+
if association_eager_loads.present?
|
55
|
+
eager_load({ association_name => association_eager_loads})
|
56
|
+
else
|
57
|
+
eager_load(association_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def decorates_associations(*arguments)
|
62
|
+
options = arguments.extract_options!
|
63
|
+
association_list = arguments
|
64
|
+
|
65
|
+
association_list.each do |association_name|
|
66
|
+
decorates_association(association_name, options.dup)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
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
|
77
|
+
|
78
|
+
super(collection_with_eager_loads, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def decorate(object, options = {})
|
82
|
+
object_class = object.class
|
83
|
+
|
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
|
92
|
+
|
93
|
+
super(object, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def already_eager_loaded_associations?(object)
|
99
|
+
if object.respond_to?(:association_cache)
|
100
|
+
object.association_cache.any?
|
101
|
+
else
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def responds_to_methods?(object, *methods)
|
107
|
+
methods.each do |method_name|
|
108
|
+
return false unless object.respond_to?(method_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def link_delegations_to_object(delegated_attrs)
|
115
|
+
delegated_attrs.each do |delegate_method|
|
116
|
+
@_attributes.add(delegate_method)
|
117
|
+
|
118
|
+
unless method_defined?(delegate_method)
|
119
|
+
define_method(delegate_method) do
|
120
|
+
object.send(delegate_method)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def link_mappings_to_object(mapped_attrs)
|
127
|
+
mapped_attrs.each do |decorator_attribute, object_method|
|
128
|
+
@_attributes.add(decorator_attribute)
|
129
|
+
|
130
|
+
define_method(decorator_attribute) do
|
131
|
+
object.send(object_method)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tint
|
2
|
+
module JsonConversion
|
3
|
+
def to_json(options={})
|
4
|
+
as_json.to_json(options)
|
5
|
+
end
|
6
|
+
|
7
|
+
def as_json(options={})
|
8
|
+
attributes_for_json
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
module AttributeNameStrategy
|
14
|
+
class Stringify
|
15
|
+
class << self
|
16
|
+
def transform(attribute_name)
|
17
|
+
attribute_name.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Camelize
|
23
|
+
class << self
|
24
|
+
def transform(attribute_name)
|
25
|
+
Stringify.transform(attribute_name).camelize(:lower)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Snakize
|
31
|
+
class << self
|
32
|
+
def transform(attribute_name)
|
33
|
+
Stringify.transform(attribute_name).underscore
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Kebabize
|
39
|
+
class << self
|
40
|
+
def transform(attribute_name)
|
41
|
+
Stringify.transform(attribute_name).dasherize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_for_json
|
48
|
+
strategy =
|
49
|
+
case Tint.camelize_attribute_names
|
50
|
+
when :camel_case
|
51
|
+
AttributeNameStrategy::Camelize
|
52
|
+
when :snake_case
|
53
|
+
AttributeNameStrategy::Snakize
|
54
|
+
when :kebab_case
|
55
|
+
AttributeNameStrategy::Kebabize
|
56
|
+
else
|
57
|
+
AttributeNameStrategy::Stringify
|
58
|
+
end
|
59
|
+
|
60
|
+
self.class._attributes.inject({}) do |memo, key_and_value|
|
61
|
+
key, _ = key_and_value
|
62
|
+
|
63
|
+
unless (value = self.send(key)).nil?
|
64
|
+
memo[strategy.transform(key)] = value.respond_to?(:as_json) ? value.as_json : value
|
65
|
+
end
|
66
|
+
|
67
|
+
memo
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/tint/version.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'tint'
|
2
|
+
require_relative '../support/active_record_mock'
|
3
|
+
|
4
|
+
RSpec.describe Tint::Decorator do
|
5
|
+
subject{ decorator_class.decorate(object).as_json }
|
6
|
+
|
7
|
+
let(:object_class) do
|
8
|
+
Class.new(ActiveRecordMock) do
|
9
|
+
attr_reader :attr1, :attr2
|
10
|
+
|
11
|
+
def initialize(attr1, attr2)
|
12
|
+
@attr1, @attr2 = attr1, attr2
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:object) { object_class.new('one', 'two') }
|
18
|
+
|
19
|
+
describe "::attributes" do
|
20
|
+
context "when only delegations are defined" do
|
21
|
+
let(:decorator_class) do
|
22
|
+
Class.new(Tint::Decorator) do
|
23
|
+
attributes :attr1, :attr2
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "delegates attributes of the same name to the object" do
|
28
|
+
expect(subject['attr1']).to eql('one')
|
29
|
+
expect(subject['attr2']).to eql('two')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when delegations and mappings are defined" do
|
34
|
+
let(:decorator_class) do
|
35
|
+
Class.new(Tint::Decorator) do
|
36
|
+
attributes :attr1, decorated1: :attr2
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "delegates attributes of the same name to the object" do
|
41
|
+
expect(subject['attr1']).to eql('one')
|
42
|
+
expect(subject['decorated1']).to eql('two')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when only mappings are defined" do
|
47
|
+
let(:decorator_class) do
|
48
|
+
Class.new(Tint::Decorator) do
|
49
|
+
attributes decorated1: :attr1, decorated2: :attr2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "delegates attributes of the same name to the object" do
|
54
|
+
expect(subject['decorated1']).to eql('one')
|
55
|
+
expect(subject['decorated2']).to eql('two')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "::decorates_association" do
|
61
|
+
let(:associated_decorator) do
|
62
|
+
Class.new(Tint::Decorator) do
|
63
|
+
attributes :attr1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:associated_object) do
|
68
|
+
Class.new(ActiveRecordMock) do
|
69
|
+
attr_reader :attr1
|
70
|
+
|
71
|
+
def initialize(attribute_value)
|
72
|
+
@attr1 = attribute_value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:main_object) do
|
78
|
+
Class.new(Tint::Decorator) do
|
79
|
+
attr_reader :associated
|
80
|
+
|
81
|
+
def initialize(associated)
|
82
|
+
@associated = associated
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:object) { main_object.new(associated_object.new('value')) }
|
88
|
+
|
89
|
+
context "when no with option is provided" do
|
90
|
+
let(:decorator_class) do
|
91
|
+
Class.new(Tint::Decorator) do
|
92
|
+
decorates_association :associated
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
before(:each) do
|
97
|
+
Object.const_set('AssociatedDecorator', associated_decorator)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "attempts to use a decorator with the same name as the association" do
|
101
|
+
expect(subject['associated']['attr1']).to eql('value')
|
102
|
+
end
|
103
|
+
|
104
|
+
after(:each) do
|
105
|
+
Object.send(:remove_const, 'AssociatedDecorator')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when a with option is provided" do
|
110
|
+
let(:explicitly_referenced_decorator) do
|
111
|
+
Class.new(Tint::Decorator) do
|
112
|
+
attributes decorated1: :attr1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
let(:decorator_class) do
|
117
|
+
Class.new(Tint::Decorator) do
|
118
|
+
decorates_association :associated, with: ExplicitlyReferencedDecorator
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
before(:each) do
|
123
|
+
Object.const_set('ExplicitlyReferencedDecorator', explicitly_referenced_decorator)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "uses the specified decorator" do
|
127
|
+
expect(subject['associated']['decorated1']).to eql('value')
|
128
|
+
end
|
129
|
+
|
130
|
+
after(:each) do
|
131
|
+
Object.send(:remove_const, 'ExplicitlyReferencedDecorator')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "::eager_load" do
|
137
|
+
it "adds the associations to the list to eager load" do
|
138
|
+
|
139
|
+
eager_load_schemas = [
|
140
|
+
[:associated],
|
141
|
+
[ { associated1: [:associated2] } ],
|
142
|
+
[ { associated1: { associated2: [:associated3] } } ],
|
143
|
+
[ :associated1, { associated2: [:associated3] } ]
|
144
|
+
]
|
145
|
+
|
146
|
+
eager_load_schemas.each do |eager_load_schema|
|
147
|
+
decorator_class =
|
148
|
+
Class.new(Tint::Decorator) do
|
149
|
+
eager_load *eager_load_schema
|
150
|
+
end
|
151
|
+
|
152
|
+
eager_load_schema.each do |schema|
|
153
|
+
expect(decorator_class.eager_loads).to include(schema)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/tint.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tint/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tint"
|
8
|
+
spec.version = Tint::VERSION
|
9
|
+
spec.authors = ["Aleck Greenham"]
|
10
|
+
spec.email = ["greenhama13@gmail.com"]
|
11
|
+
spec.summary = "Declarative object decorators for JSON APIs"
|
12
|
+
spec.description = "Easily define object decorators for JSON APIs using simple declarative syntax"
|
13
|
+
spec.homepage = "https://github.com/greena13/tint"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "draper", "~> 2.1"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
23
|
+
spec.add_development_dependency "rake", "~> 0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 0"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aleck Greenham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: draper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Easily define object decorators for JSON APIs using simple declarative
|
70
|
+
syntax
|
71
|
+
email:
|
72
|
+
- greenhama13@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/tint.rb
|
84
|
+
- lib/tint/decorator.rb
|
85
|
+
- lib/tint/json_conversion.rb
|
86
|
+
- lib/tint/version.rb
|
87
|
+
- spec/support/active_record_mock.rb
|
88
|
+
- spec/tint/decorator_spec.rb
|
89
|
+
- tint.gemspec
|
90
|
+
homepage: https://github.com/greena13/tint
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.2.2
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Declarative object decorators for JSON APIs
|
114
|
+
test_files:
|
115
|
+
- spec/support/active_record_mock.rb
|
116
|
+
- spec/tint/decorator_spec.rb
|