halogen 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 +15 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +244 -0
- data/Rakefile +6 -0
- data/examples/extensions.md +25 -0
- data/examples/simple.rb +142 -0
- data/halogen.gemspec +32 -0
- data/lib/halogen.rb +157 -0
- data/lib/halogen/collection.rb +44 -0
- data/lib/halogen/configuration.rb +15 -0
- data/lib/halogen/definition.rb +73 -0
- data/lib/halogen/definitions.rb +23 -0
- data/lib/halogen/embeds.rb +105 -0
- data/lib/halogen/embeds/definition.rb +19 -0
- data/lib/halogen/errors.rb +7 -0
- data/lib/halogen/hash_util.rb +37 -0
- data/lib/halogen/links.rb +41 -0
- data/lib/halogen/links/definition.rb +59 -0
- data/lib/halogen/properties.rb +40 -0
- data/lib/halogen/properties/definition.rb +8 -0
- data/lib/halogen/railtie.rb +11 -0
- data/lib/halogen/resource.rb +58 -0
- data/lib/halogen/version.rb +5 -0
- data/spec/halogen/collection_spec.rb +78 -0
- data/spec/halogen/configuration_spec.rb +11 -0
- data/spec/halogen/definition_spec.rb +108 -0
- data/spec/halogen/definitions_spec.rb +24 -0
- data/spec/halogen/embeds/definition_spec.rb +23 -0
- data/spec/halogen/embeds_spec.rb +201 -0
- data/spec/halogen/links/definition_spec.rb +68 -0
- data/spec/halogen/links_spec.rb +59 -0
- data/spec/halogen/properties_spec.rb +41 -0
- data/spec/halogen/resource_spec.rb +77 -0
- data/spec/halogen_spec.rb +127 -0
- data/spec/spec_helper.rb +11 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 491dfa5f8eb8f51c4973205d1fbab6e883ce40db
|
4
|
+
data.tar.gz: 79c6b3b9b20ba7e86b6684daa38440b60b34460c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a69872769a3355f338b642ff0ae66dcbbc37a76edb9e74f7826e3abf7c53d8525ef8399a042db3d4d38b971d3bd7851984395779af628cd1df99e10983224024
|
7
|
+
data.tar.gz: d6ba44f85ce1d6b5bf1108975aa3426354f8f1b32d3cfc62bd16d91d9f894ac14e8186d8ca6121a561d0e5659cef16b7a81c9421ea386328421329c98c5b4b06
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Heather Rivers
|
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,244 @@
|
|
1
|
+
# Halogen
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mode/halogen)
|
4
|
+
|
5
|
+
This library provides a framework-agnostic interface for generating
|
6
|
+
[HAL+JSON](http://stateless.co/hal_specification.html)
|
7
|
+
representations of resources in Ruby.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'halogen'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install halogen
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Basic usage
|
28
|
+
|
29
|
+
Create a simple representer class and include Halogen:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class GoatRepresenter
|
33
|
+
include Halogen
|
34
|
+
|
35
|
+
property :name do
|
36
|
+
'Gideon'
|
37
|
+
end
|
38
|
+
|
39
|
+
link :self do
|
40
|
+
'/goats/gideon'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Instantiate:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
repr = GoatRepresenter.new
|
49
|
+
```
|
50
|
+
|
51
|
+
Then call `repr.render`:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
{
|
55
|
+
name: 'Gideon',
|
56
|
+
_links: {
|
57
|
+
self: { href: '/goats/gideon' }
|
58
|
+
}
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
Or `repr.to_json`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
'{"name": "Gideon", "_links": {"self": {"href": "/goats/gideon"}}}'
|
66
|
+
```
|
67
|
+
|
68
|
+
### Representer types
|
69
|
+
|
70
|
+
#### 1. Simple
|
71
|
+
|
72
|
+
Not associated with any particular resource or collection. For example, an API
|
73
|
+
entry point:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class ApiRootRepresenter
|
77
|
+
include Halogen
|
78
|
+
|
79
|
+
link(:self) { '/api' }
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
#### 2. Resource
|
84
|
+
|
85
|
+
Represents a single item:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class GoatRepresenter
|
89
|
+
include Halogen
|
90
|
+
|
91
|
+
resource :goat
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
When a resource is declared, `#initialize` expects the resource as the first argument:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
repr = GoatRepresenter.new(Goat.new, ...)
|
99
|
+
```
|
100
|
+
|
101
|
+
This makes property definitions cleaner:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
property :name # now calls Goat#name by default
|
105
|
+
```
|
106
|
+
|
107
|
+
#### 3. Collection
|
108
|
+
|
109
|
+
Represents a collection of items. When a collection is declared, the embedded
|
110
|
+
resource with the same name will always be embedded, whether it is requested
|
111
|
+
via standard embed options or not.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class GoatKidsRepresenter
|
115
|
+
include Halogen
|
116
|
+
|
117
|
+
collection :kids
|
118
|
+
|
119
|
+
embed(:kids) { ... } # always embedded
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
### Defining properties, links and embeds
|
124
|
+
|
125
|
+
Properties can be defined in several ways:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
property(:age) { "#{goat.age} years old" }
|
129
|
+
```
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
property :age # => Goat#age, if resource is declared
|
133
|
+
```
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
property :age do
|
137
|
+
goat.age.round
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
property(:age) { calculate_age }
|
143
|
+
|
144
|
+
def calculate_age
|
145
|
+
...
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
#### Conditionals
|
150
|
+
|
151
|
+
The inclusion of properties can be determined by conditionals using `if` and
|
152
|
+
`unless` options. For example, with a method name:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
property :age, if: :include_age?
|
156
|
+
|
157
|
+
def include_age?
|
158
|
+
goat.age < 10
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
With a proc:
|
163
|
+
```ruby
|
164
|
+
property :age, unless: proc { goat.age.nil? }, value: ...
|
165
|
+
```
|
166
|
+
|
167
|
+
For links and embeds:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
link :kids, :templated, unless: :exclude_kids_link?, value: ...
|
171
|
+
```
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
embed :kids, if: proc { goat.kids.size > 0 } do
|
175
|
+
...
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
#### Links
|
180
|
+
|
181
|
+
Simple link:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
link(:root) { '/' }
|
185
|
+
# => { _links: { root: { href: '/' } } ... }
|
186
|
+
```
|
187
|
+
|
188
|
+
Templated link:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
link(:find, :templated) { '/goats/{?id}' }
|
192
|
+
# => { _links: { find: { href: '/goats/{?id}', templated: true } } ... }
|
193
|
+
```
|
194
|
+
|
195
|
+
### Embedded resources
|
196
|
+
|
197
|
+
Embedded resources are not rendered by default. They will be included if both
|
198
|
+
of the following conditions are met:
|
199
|
+
|
200
|
+
1. The proc returns either a Halogen instance or an array of Halogen instances
|
201
|
+
2. The embed is requested via the parent representer's options, e.g.:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
GoatRepresenter.new(embed: { kids: true, parents: false })
|
205
|
+
```
|
206
|
+
|
207
|
+
Embedded resources can be nested to any depth, e.g.:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
GoatRepresenter.new(embed: {
|
211
|
+
kids: {
|
212
|
+
foods: {
|
213
|
+
ingredients: true
|
214
|
+
},
|
215
|
+
pasture: true
|
216
|
+
}
|
217
|
+
})
|
218
|
+
```
|
219
|
+
|
220
|
+
### Using with Rails
|
221
|
+
|
222
|
+
If Halogen is loaded in a Rails application, Rails url helpers will be
|
223
|
+
available in representers:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
link(:new) { new_goat_url }
|
227
|
+
```
|
228
|
+
|
229
|
+
### More examples
|
230
|
+
|
231
|
+
* [Full representer class](examples/simple.rb)
|
232
|
+
* [Extensions](examples/extensions.md)
|
233
|
+
|
234
|
+
### What's with the goat?
|
235
|
+
|
236
|
+
It is [majestic](https://twitter.com/ModeAnalytics/status/497876416013537280).
|
237
|
+
|
238
|
+
## Contributing
|
239
|
+
|
240
|
+
1. Fork it ( https://github.com/mode/halogen/fork )
|
241
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
242
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
243
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
244
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Extensions
|
2
|
+
|
3
|
+
You can extend Halogen by configuring it to include your own Ruby modules.
|
4
|
+
|
5
|
+
For instance, if you wanted to cache the rendered versions of your
|
6
|
+
representers, you might use something like this to override the default
|
7
|
+
`#render` behavior:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
module MyCachingExtension
|
11
|
+
def render
|
12
|
+
Rails.cache.fetch(cache_key) { super }
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache_key
|
16
|
+
...
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Halogen.configure do |config|
|
23
|
+
config.extensions << MyCachingExtension
|
24
|
+
end
|
25
|
+
```
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
require 'halogen'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
# Example of a straightforward Halogen representer with no resource,
|
9
|
+
# collection, or conditional definitions
|
10
|
+
#
|
11
|
+
class GoatRepresenter
|
12
|
+
include Halogen
|
13
|
+
|
14
|
+
# Simple instance methods that will be used for properties below
|
15
|
+
#
|
16
|
+
def id; 1; end
|
17
|
+
def first_name; 'Gideon'; end
|
18
|
+
def last_name; 'Goat'; end
|
19
|
+
|
20
|
+
# == 1. Properties
|
21
|
+
#
|
22
|
+
# If you define a property without an explicit value or proc, Halogen will
|
23
|
+
# look for a public instance method with the corresponding name.
|
24
|
+
#
|
25
|
+
# This will call GoatRepresenter#id.
|
26
|
+
#
|
27
|
+
property :id # => { id: 1 }
|
28
|
+
|
29
|
+
# You can also define a property with an explicit value, e.g.:
|
30
|
+
#
|
31
|
+
property :age, value: 9.2 # => { age: 9.2 }
|
32
|
+
|
33
|
+
# Or you can use a proc to determine the property value at render time.
|
34
|
+
#
|
35
|
+
# The example below could also be written: property(:full_name) { ... }
|
36
|
+
#
|
37
|
+
property :full_name do # => { full_name: 'Gideon Goat' }
|
38
|
+
"#{first_name} #{last_name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# == 2. Links
|
42
|
+
#
|
43
|
+
# As with properties, links can be defined with a proc:
|
44
|
+
#
|
45
|
+
link :self do
|
46
|
+
"/goats/#{id}" # => { self: { href: '/goats/1' } }
|
47
|
+
end
|
48
|
+
|
49
|
+
# ...Or with an explicit value:
|
50
|
+
#
|
51
|
+
link :root, value: '/goats' # => { root: { href: '/goats' } }
|
52
|
+
|
53
|
+
# Links can also be defined as "templated", following HAL+JSON conventions:
|
54
|
+
#
|
55
|
+
link :find, :templated do # => ... { href: '/goats/{?id}', templated: true }
|
56
|
+
'/goats/{?id}'
|
57
|
+
end
|
58
|
+
|
59
|
+
# If Halogen is loaded in a Rails application, url helpers will be available
|
60
|
+
# automatically:
|
61
|
+
#
|
62
|
+
# link(:new) { new_goat_path }
|
63
|
+
|
64
|
+
# == 3. Embeds
|
65
|
+
#
|
66
|
+
# Embedded resources are not rendered by default. They will be included if
|
67
|
+
# both of the following conditions are met:
|
68
|
+
#
|
69
|
+
# 1. The proc returns either a Halogen instance or an array of Halogen instances
|
70
|
+
# 2. The embed is requested via the parent representer's options, e.g.:
|
71
|
+
#
|
72
|
+
# GoatRepresenter.new(embed: { kids: true, parents: false })
|
73
|
+
#
|
74
|
+
embed :kids do # => { kids: <GoatKidsRepresenter#render> }
|
75
|
+
GoatKidsRepresenter.new
|
76
|
+
end
|
77
|
+
|
78
|
+
embed :parents do # => will not be included according to example options above
|
79
|
+
[
|
80
|
+
self.class.new,
|
81
|
+
self.class.new
|
82
|
+
]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Embedded resources can be nested to any depth, e.g.:
|
86
|
+
#
|
87
|
+
# GoatRepresenter.new(embed: {
|
88
|
+
# kids: {
|
89
|
+
# foods: {
|
90
|
+
# ingredients: true
|
91
|
+
# },
|
92
|
+
# enclosure: true
|
93
|
+
# }
|
94
|
+
# })
|
95
|
+
end
|
96
|
+
|
97
|
+
# Another simple representer to demonstrate embedded resources above
|
98
|
+
#
|
99
|
+
class GoatKidsRepresenter
|
100
|
+
include Halogen
|
101
|
+
|
102
|
+
property :count, value: 5
|
103
|
+
end
|
104
|
+
|
105
|
+
puts 'GoatRepresenter.new(embed: { kids: true }).render:'
|
106
|
+
puts
|
107
|
+
pp GoatRepresenter.new(embed: { kids: true }).render
|
108
|
+
#
|
109
|
+
# Result:
|
110
|
+
#
|
111
|
+
# {
|
112
|
+
# id: 1,
|
113
|
+
# age: 9.2,
|
114
|
+
# full_name: "Gideon Goat",
|
115
|
+
# _embedded: {
|
116
|
+
# kids: { count: 5 }
|
117
|
+
# },
|
118
|
+
# _links: {
|
119
|
+
# self: { href: '/goats/1' },
|
120
|
+
# root: { href: '/goats"'},
|
121
|
+
# find: { href: '/goats/{?id}', templated: true }
|
122
|
+
# }
|
123
|
+
# }
|
124
|
+
#
|
125
|
+
|
126
|
+
puts
|
127
|
+
puts 'GoatRepresenter.new.render:'
|
128
|
+
puts
|
129
|
+
pp GoatRepresenter.new.render
|
130
|
+
#
|
131
|
+
# Result:
|
132
|
+
#
|
133
|
+
# {
|
134
|
+
# id: 1,
|
135
|
+
# age: 9.2,
|
136
|
+
# full_name: "Gideon Goat",
|
137
|
+
# _links: {
|
138
|
+
# self: { href: '/goats/1' },
|
139
|
+
# root: { href: '/goats"'},
|
140
|
+
# find: { href: '/goats/{?id}', templated: true }
|
141
|
+
# }
|
142
|
+
# }
|