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
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
module Embeds
|
5
|
+
class Definition < Halogen::Definition # :nodoc:
|
6
|
+
# @return [true] if nothing is raised
|
7
|
+
#
|
8
|
+
# @raise [Halogen::InvalidDefinition] if the definition is invalid
|
9
|
+
#
|
10
|
+
def validate
|
11
|
+
super
|
12
|
+
|
13
|
+
return true if procedure
|
14
|
+
|
15
|
+
fail InvalidDefinition, "Embed #{name} must be defined with a proc"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Halogen
|
2
|
+
module HashUtil # :nodoc:
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Transform hash keys into strings if necessary
|
6
|
+
#
|
7
|
+
# @param hash [Hash]
|
8
|
+
#
|
9
|
+
# @return [Hash]
|
10
|
+
#
|
11
|
+
def stringify_keys!(hash)
|
12
|
+
transform_keys!(hash, &:to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Transform hash keys into symbols if necessary
|
16
|
+
#
|
17
|
+
# @param hash [Hash]
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
def symbolize_keys!(hash)
|
22
|
+
transform_keys!(hash, &:to_sym)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Transform hash keys according to block
|
26
|
+
#
|
27
|
+
# @param hash [Hash]
|
28
|
+
#
|
29
|
+
# @return [Hash]
|
30
|
+
#
|
31
|
+
def transform_keys!(hash)
|
32
|
+
hash.keys.each { |key| hash[yield(key)] = hash.delete(key) }
|
33
|
+
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
module Links # :nodoc:
|
5
|
+
def self.included(base) # :nodoc:
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods # :nodoc:
|
12
|
+
# @return [Halogen::Embeds::Definition]
|
13
|
+
#
|
14
|
+
def link(name, *args, &procedure)
|
15
|
+
definitions.add(Definition.new(name, *args, procedure))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods # :nodoc:
|
20
|
+
# @return [Hash] the rendered hash with links, if any
|
21
|
+
#
|
22
|
+
def render
|
23
|
+
decorate_render :links, super
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash] links from definitions
|
27
|
+
#
|
28
|
+
def links
|
29
|
+
render_definitions(Definition.name) do |definition, result|
|
30
|
+
attrs = definition.options.fetch(:attrs, {})
|
31
|
+
|
32
|
+
href = definition.value(self)
|
33
|
+
|
34
|
+
result[definition.name] = attrs.merge(href: href) if href
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
require 'halogen/links/definition'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
module Links
|
5
|
+
class Definition < Halogen::Definition # :nodoc
|
6
|
+
# Links have special keywords that other definitions don't, so override
|
7
|
+
# the standard initializer to build options from keywords
|
8
|
+
#
|
9
|
+
def initialize(name, *args, procedure)
|
10
|
+
super name, self.class.build_options(args), procedure
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [true] if nothing is raised
|
14
|
+
#
|
15
|
+
# @raise [Halogen::InvalidDefinition] if the definition is invalid
|
16
|
+
#
|
17
|
+
def validate
|
18
|
+
super
|
19
|
+
|
20
|
+
return true if procedure || options.key?(:value)
|
21
|
+
|
22
|
+
fail InvalidDefinition,
|
23
|
+
'Link requires either procedure or explicit value'
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# Build hash of options from flexible definition arguments
|
28
|
+
#
|
29
|
+
# @param args [Array] the raw definition arguments
|
30
|
+
#
|
31
|
+
# @return [Hash] standardized hash of options
|
32
|
+
#
|
33
|
+
def build_options(args)
|
34
|
+
{}.tap do |options|
|
35
|
+
options.merge!(args.pop) if args.last.is_a?(Hash)
|
36
|
+
|
37
|
+
options[:attrs] ||= {}
|
38
|
+
options[:attrs].merge!(build_attrs(args))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param keywords [Array] array of special keywords
|
43
|
+
#
|
44
|
+
# @raise [Halogen::InvalidDefinition] if a keyword is unrecognized
|
45
|
+
#
|
46
|
+
def build_attrs(keywords)
|
47
|
+
keywords.each_with_object({}) do |keyword, attrs|
|
48
|
+
case keyword
|
49
|
+
when :templated, 'templated'
|
50
|
+
attrs[:templated] = true
|
51
|
+
else
|
52
|
+
fail InvalidDefinition, "Unrecognized link keyword: #{keyword}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
module Properties # :nodoc:
|
5
|
+
def self.included(base) # :nodoc:
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods # :nodoc:
|
12
|
+
# @param name [Symbol, String]
|
13
|
+
# @param options [nil, Hash]
|
14
|
+
#
|
15
|
+
# @return [Halogen::Embeds::Definition]
|
16
|
+
#
|
17
|
+
def property(name, options = {}, &procedure)
|
18
|
+
definitions.add(Definition.new(name, options, procedure))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods # :nodoc:
|
23
|
+
# @return [Hash] the rendered hash with properties, if any
|
24
|
+
#
|
25
|
+
def render
|
26
|
+
super.merge(properties)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Hash] properties from definitions
|
30
|
+
#
|
31
|
+
def properties
|
32
|
+
render_definitions(Definition.name) do |definition, result|
|
33
|
+
result[definition.name] = definition.value(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'halogen/properties/definition'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
# Provide Rails-specific extensions if loaded in a Rails application
|
5
|
+
#
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
initializer 'halogen' do |_app|
|
8
|
+
Halogen.config.extensions << ::Rails.application.routes.url_helpers
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
module Halogen
|
4
|
+
# Behavior for representers with a single primary resource
|
5
|
+
#
|
6
|
+
module Resource
|
7
|
+
def self.included(base) # :nodoc:
|
8
|
+
if base.included_modules.include?(Collection)
|
9
|
+
fail InvalidResource, "#{base.name} has already defined a collection"
|
10
|
+
end
|
11
|
+
|
12
|
+
base.extend ClassMethods
|
13
|
+
|
14
|
+
base.send :include, InstanceMethods
|
15
|
+
|
16
|
+
base.send :attr_reader, :resource
|
17
|
+
|
18
|
+
base.class.send :attr_accessor, :resource_name
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods # :nodoc:
|
22
|
+
# @param name [Symbol, String] name of the resource
|
23
|
+
#
|
24
|
+
# @return [Module] self
|
25
|
+
#
|
26
|
+
def define_resource(name)
|
27
|
+
self.resource_name = name.to_s
|
28
|
+
|
29
|
+
alias_method name, :resource
|
30
|
+
end
|
31
|
+
|
32
|
+
# Override standard property definition for resource-based representers
|
33
|
+
#
|
34
|
+
# @param name [Symbol, String] name of the property
|
35
|
+
# @param options [nil, Hash] property options for definition
|
36
|
+
#
|
37
|
+
def property(name, options = {}, &procedure)
|
38
|
+
super.tap do |definition|
|
39
|
+
unless definition.procedure || definition.options.key?(:value)
|
40
|
+
definition.procedure = proc { resource.send(name) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods # :nodoc:
|
47
|
+
# Override standard initializer to assign primary resource
|
48
|
+
#
|
49
|
+
# @param resource [Object] the primary resource
|
50
|
+
#
|
51
|
+
def initialize(resource, *args)
|
52
|
+
@resource = resource
|
53
|
+
|
54
|
+
super *args
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Halogen::Collection do
|
6
|
+
let :klass do
|
7
|
+
Class.new do
|
8
|
+
include Halogen
|
9
|
+
include Halogen::Collection
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.included' do
|
14
|
+
it 'raises error if base is already a resource' do
|
15
|
+
resource_class = Class.new do
|
16
|
+
include Halogen
|
17
|
+
include Halogen::Resource
|
18
|
+
end
|
19
|
+
|
20
|
+
expect {
|
21
|
+
resource_class.send :include, Halogen::Collection
|
22
|
+
}.to raise_error do |exception|
|
23
|
+
expect(exception).to be_an_instance_of(Halogen::InvalidCollection)
|
24
|
+
expect(exception.message).to match(/has already defined a resource/i)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Halogen::Collection::ClassMethods do
|
30
|
+
describe '#define_collection' do
|
31
|
+
it 'handles string argument' do
|
32
|
+
klass.define_collection 'goats'
|
33
|
+
|
34
|
+
expect(klass.collection_name).to eq('goats')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'handles symbol argument' do
|
38
|
+
klass.define_collection :goats
|
39
|
+
|
40
|
+
expect(klass.collection_name).to eq('goats')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Halogen::Collection::InstanceMethods do
|
46
|
+
describe '#embed?' do
|
47
|
+
it 'returns true if super is true' do
|
48
|
+
klass.collection_name = 'bar'
|
49
|
+
|
50
|
+
klass.embed(:foo) { klass.new }
|
51
|
+
|
52
|
+
repr = klass.new(embed: { foo: 1 })
|
53
|
+
|
54
|
+
expect(repr.embed?('foo')).to eq(true)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns true if key matches collection name' do
|
58
|
+
klass.collection_name = 'foo'
|
59
|
+
|
60
|
+
klass.embed(:foo) { klass.new }
|
61
|
+
|
62
|
+
repr = klass.new(embed: { foo: 0 })
|
63
|
+
|
64
|
+
expect(repr.embed?('foo')).to eq(true)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns false if no conditions match' do
|
68
|
+
klass.collection_name = 'bar'
|
69
|
+
|
70
|
+
klass.embed(:foo) { klass.new }
|
71
|
+
|
72
|
+
repr = klass.new(embed: { foo: 0 })
|
73
|
+
|
74
|
+
expect(repr.embed?('foo')).to eq(false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Halogen::Definition do
|
6
|
+
describe '#initialize' do
|
7
|
+
it 'symbolizes option keys' do
|
8
|
+
definition = Halogen::Definition.new(
|
9
|
+
:name, { 'value' => 'some value', 'foo' => 'bar' }, nil)
|
10
|
+
|
11
|
+
expect(definition.options.keys).to eq([:value, :foo])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#value' do
|
16
|
+
it 'returns value from options if present' do
|
17
|
+
definition = Halogen::Definition.new(:name, { value: 'some value' }, nil)
|
18
|
+
|
19
|
+
expect(definition.value(nil)).to eq('some value')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'evaluates procedure if value from options is missing' do
|
23
|
+
definition = Halogen::Definition.new(:name, {}, proc { size })
|
24
|
+
|
25
|
+
expect(definition.value('foo')).to eq(3)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#enabled?' do
|
30
|
+
it 'is true if definition is not guarded' do
|
31
|
+
definition = Halogen::Definition.new(:name, {}, nil)
|
32
|
+
|
33
|
+
expect(definition.enabled?(nil)).to eq(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'when guard is a proc' do
|
37
|
+
let :definition do
|
38
|
+
Halogen::Definition.new(:name, { if: proc { empty? } }, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'is true if condition passes' do
|
42
|
+
expect(definition.enabled?('')).to eq(true)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'is false if condition fails' do
|
46
|
+
expect(definition.enabled?('foo')).to eq(false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'when guard is a method name' do
|
51
|
+
let :definition do
|
52
|
+
Halogen::Definition.new(:name, { if: :empty? }, nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'is true if condition passes' do
|
56
|
+
expect(definition.enabled?('')).to eq(true)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'is false if condition fails' do
|
60
|
+
expect(definition.enabled?('foo')).to eq(false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'when guard is truthy' do
|
65
|
+
it 'is true if condition passes' do
|
66
|
+
definition = Halogen::Definition.new(:name, { if: true }, nil)
|
67
|
+
|
68
|
+
expect(definition.enabled?(nil)).to eq(true)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'is false if condition fails' do
|
72
|
+
definition = Halogen::Definition.new(:name, { if: false }, nil)
|
73
|
+
|
74
|
+
expect(definition.enabled?(nil)).to eq(false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'when guard is negated' do
|
79
|
+
let :definition do
|
80
|
+
Halogen::Definition.new(:name, { unless: proc { empty? } }, nil)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'is false if condition passes' do
|
84
|
+
expect(definition.enabled?('')).to eq(false)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#validate' do
|
90
|
+
it 'returns true for valid definition' do
|
91
|
+
definition = Halogen::Definition.new(:name, { value: 'value' }, nil)
|
92
|
+
|
93
|
+
expect(definition.validate).to eq(true)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'raises error for invalid definition' do
|
97
|
+
definition = Halogen::Definition.new(
|
98
|
+
:name, { value: 'value' }, proc { 'value' })
|
99
|
+
|
100
|
+
expect {
|
101
|
+
definition.validate
|
102
|
+
}.to raise_error(Halogen::InvalidDefinition) do |exception|
|
103
|
+
expect(exception.message).to(
|
104
|
+
eq('Cannot specify both value and procedure for name'))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|