halogen 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Halogen
4
+ class InvalidCollection < StandardError; end # :nodoc
5
+ class InvalidDefinition < StandardError; end # :nodoc
6
+ class InvalidResource < StandardError; end # :nodoc
7
+ 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,8 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Halogen
4
+ module Properties
5
+ class Definition < Halogen::Definition # :nodoc
6
+ end
7
+ end
8
+ end
@@ -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,5 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Halogen
4
+ VERSION = '0.0.1' # :nodoc:
5
+ 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,11 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Halogen::Configuration do
6
+ describe '#extensions' do
7
+ it 'is empty array by default' do
8
+ expect(Halogen::Configuration.new.extensions).to eq([])
9
+ end
10
+ end
11
+ 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