halogen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|