halitosis 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -19
- data/lib/halitosis/attributes/field.rb +8 -0
- data/lib/halitosis/attributes.rb +52 -0
- data/lib/halitosis/base.rb +27 -31
- data/lib/halitosis/collection/field.rb +11 -0
- data/lib/halitosis/collection.rb +40 -27
- data/lib/halitosis/context.rb +54 -0
- data/lib/halitosis/errors.rb +13 -0
- data/lib/halitosis/field.rb +8 -23
- data/lib/halitosis/fields.rb +6 -0
- data/lib/halitosis/hash_util.rb +31 -12
- data/lib/halitosis/{properties → identifiers}/field.rb +1 -1
- data/lib/halitosis/identifiers.rb +46 -0
- data/lib/halitosis/links/field.rb +1 -1
- data/lib/halitosis/links.rb +6 -6
- data/lib/halitosis/meta.rb +7 -7
- data/lib/halitosis/permissions.rb +7 -7
- data/lib/halitosis/railtie.rb +10 -1
- data/lib/halitosis/relationships/field.rb +2 -2
- data/lib/halitosis/relationships.rb +25 -17
- data/lib/halitosis/resource.rb +37 -11
- data/lib/halitosis/root_links/field.rb +8 -0
- data/lib/halitosis/root_links.rb +46 -0
- data/lib/halitosis/root_meta/field.rb +8 -0
- data/lib/halitosis/root_meta.rb +44 -0
- data/lib/halitosis/root_permissions/field.rb +8 -0
- data/lib/halitosis/root_permissions.rb +43 -0
- data/lib/halitosis/version.rb +1 -1
- data/lib/halitosis.rb +20 -3
- metadata +13 -4
- data/lib/halitosis/properties.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0576b2d1e7ce15303f1bdc4a845bc7e81d1f2e497d3e49b220f1dd68920938cb
|
4
|
+
data.tar.gz: 79a5d29e8ec21cf3f035719dab8ef719216424a8514c2ff045d8f0f0d1cbe552
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '02779ecf3fd87d66cab25c245c1ca82a2eee370241088d1af6fcc72bde1f3e7917687119260e4d5d04f22dfb363c1d8d9b54730caece6a82825d2ca65f6d9ef5'
|
7
|
+
data.tar.gz: 2e77dae400e7f5ece72aa1604898444b90c43cca04694b495f23dda4aaf38e4b62b05bc1cdd67b1f32d1e5c1014924756e2b242bd4c5a648df944bee18d61520
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Need something more standardized ([JSON:API](https://jsonapi.org/), or [HAL](htt
|
|
21
21
|
Add this line to your application's Gemfile:
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
gem "
|
24
|
+
gem "halitosis"
|
25
25
|
```
|
26
26
|
|
27
27
|
And then execute:
|
@@ -33,7 +33,7 @@ $ bundle install
|
|
33
33
|
Or install it yourself as:
|
34
34
|
|
35
35
|
```bash
|
36
|
-
$ gem install
|
36
|
+
$ gem install halitosis
|
37
37
|
```
|
38
38
|
|
39
39
|
### Basic usage
|
@@ -51,7 +51,7 @@ class DuckSerializer
|
|
51
51
|
|
52
52
|
resource :duck
|
53
53
|
|
54
|
-
|
54
|
+
attribute :name
|
55
55
|
|
56
56
|
link :self do
|
57
57
|
"/ducks/#{duck.code}"
|
@@ -68,11 +68,13 @@ serializer = DuckSerializer.new(duck)
|
|
68
68
|
|
69
69
|
Then call `serializer.render`:
|
70
70
|
|
71
|
-
```
|
71
|
+
```ruby
|
72
72
|
{
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
duck: {
|
74
|
+
name: 'Ferdi',
|
75
|
+
_links: {
|
76
|
+
self: { href: '/ducks/ferdi' }
|
77
|
+
}
|
76
78
|
}
|
77
79
|
}
|
78
80
|
```
|
@@ -80,7 +82,7 @@ Then call `serializer.render`:
|
|
80
82
|
Or `serializer.to_json`:
|
81
83
|
|
82
84
|
```ruby
|
83
|
-
'{"name": "Ferdi", "_links": {"self": {"href": "/ducks/ferdi"}}}'
|
85
|
+
'{"duck": {"name": "Ferdi", "_links": {"self": {"href": "/ducks/ferdi"}}}}'
|
84
86
|
```
|
85
87
|
|
86
88
|
|
@@ -117,10 +119,10 @@ When a resource is declared, `#initialize` expects the resource as the first arg
|
|
117
119
|
serializer = DuckSerializer.new(Duck.new, ...)
|
118
120
|
```
|
119
121
|
|
120
|
-
This makes
|
122
|
+
This makes attribute definitions cleaner:
|
121
123
|
|
122
124
|
```ruby
|
123
|
-
|
125
|
+
attribute :name # now calls Duck#name by default
|
124
126
|
```
|
125
127
|
|
126
128
|
#### 3. Collection
|
@@ -139,39 +141,51 @@ end
|
|
139
141
|
|
140
142
|
The block should return an array of Halitosis instances in order to be rendered.
|
141
143
|
|
142
|
-
### Defining
|
144
|
+
### Defining attributes, links, relationships, meta, and permissions
|
143
145
|
|
144
|
-
|
146
|
+
Attributes can be defined in several ways:
|
145
147
|
|
146
148
|
```ruby
|
147
|
-
|
149
|
+
attribute(:quacks) { "#{duck.quacks} per minute" }
|
150
|
+
```
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
attribute :quacks # => Duck#quacks, if resource is declared
|
148
154
|
```
|
149
155
|
|
150
156
|
```ruby
|
151
|
-
|
157
|
+
attribute :quacks, value: "many"
|
152
158
|
```
|
153
159
|
|
154
160
|
```ruby
|
155
|
-
|
161
|
+
attribute :quacks do
|
156
162
|
duck.quacks.round
|
157
163
|
end
|
158
164
|
```
|
159
165
|
|
160
166
|
```ruby
|
161
|
-
|
167
|
+
attribute(:quacks) { calculate_quacks }
|
162
168
|
|
163
169
|
def calculate_quacks
|
164
170
|
...
|
165
171
|
end
|
166
172
|
```
|
167
173
|
|
174
|
+
Attributes can also be implemented using the legacy `property` alias:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
property(:quacks) { "#{duck.quacks} per minute" }
|
178
|
+
property :quacks # Duck#quacks
|
179
|
+
property :quacks, value: "many"
|
180
|
+
```
|
181
|
+
|
168
182
|
#### Conditionals
|
169
183
|
|
170
|
-
The inclusion of
|
184
|
+
The inclusion of attributes can be determined by conditionals using `if` and
|
171
185
|
`unless` options. For example, with a method name:
|
172
186
|
|
173
187
|
```ruby
|
174
|
-
|
188
|
+
attribute :quacks, if: :include_quacks?
|
175
189
|
|
176
190
|
def include_quacks?
|
177
191
|
duck.quacks < 10
|
@@ -180,7 +194,7 @@ end
|
|
180
194
|
|
181
195
|
With a proc:
|
182
196
|
```ruby
|
183
|
-
|
197
|
+
attribute :quacks, unless: proc { duck.quacks.nil? }, value: ...
|
184
198
|
```
|
185
199
|
|
186
200
|
For links and relationships:
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Attributes
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Legacy alias for attribute
|
13
|
+
#
|
14
|
+
# @param name [Symbol, String]
|
15
|
+
# @param options [nil, Hash]
|
16
|
+
#
|
17
|
+
# @return [Halitosis::Attributes::Field]
|
18
|
+
def property(...)
|
19
|
+
attribute(...)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Rails-style attribute definition
|
23
|
+
#
|
24
|
+
# @param name [Symbol, String]
|
25
|
+
# @param options [nil, Hash]
|
26
|
+
#
|
27
|
+
# @return [Halitosis::Attributes::Field]
|
28
|
+
#
|
29
|
+
def attribute(name, options = {}, &procedure)
|
30
|
+
fields.add(Field.new(name, options, procedure))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
# @return [Hash] the rendered hash with attributes, if any
|
36
|
+
#
|
37
|
+
def render_with_context(context)
|
38
|
+
super.merge(attributes(context))
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Hash] attributes from fields
|
42
|
+
#
|
43
|
+
def attributes(context = build_context)
|
44
|
+
render_fields(Field, context) do |field, result|
|
45
|
+
result[field.name] = field.value(context)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require "halitosis/attributes/field"
|
data/lib/halitosis/base.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Halitosis
|
4
|
+
# Base module for all serializer classes.
|
5
|
+
#
|
6
|
+
# Include this module in your serializer class, and include any additional field-type modules
|
4
7
|
module Base
|
5
8
|
def self.included(base)
|
6
9
|
base.extend ClassMethods
|
7
10
|
|
8
11
|
base.send :include, InstanceMethods
|
9
|
-
base.send :include, Links
|
10
|
-
base.send :include, Meta
|
11
|
-
base.send :include, Permissions
|
12
|
-
base.send :include, Properties
|
13
|
-
base.send :include, Relationships
|
14
12
|
|
15
13
|
base.send :attr_reader, :options
|
14
|
+
|
15
|
+
base.class.send :attr_accessor, :resource_type
|
16
16
|
end
|
17
17
|
|
18
18
|
module ClassMethods
|
@@ -32,7 +32,7 @@ module Halitosis
|
|
32
32
|
# @return [Object] the serializer instance
|
33
33
|
#
|
34
34
|
def initialize(**options)
|
35
|
-
@options = Halitosis::HashUtil.
|
35
|
+
@options = Halitosis::HashUtil.symbolize_hash(options).freeze
|
36
36
|
end
|
37
37
|
|
38
38
|
# @return [Hash, Array] rendered JSON
|
@@ -48,21 +48,15 @@ module Halitosis
|
|
48
48
|
|
49
49
|
# @return [Hash] rendered representation
|
50
50
|
#
|
51
|
-
def render
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
# @return [nil, Object] the parent serializer, if this instance is an
|
56
|
-
# embedded child
|
57
|
-
#
|
58
|
-
def parent
|
59
|
-
@parent ||= options.fetch(:parent, nil)
|
51
|
+
def render(**options)
|
52
|
+
render_with_context(build_context(options))
|
60
53
|
end
|
61
54
|
|
62
|
-
# @
|
55
|
+
# @param context [Halitosis::Context] the context instance
|
56
|
+
# @return [Hash] the rendered hash
|
63
57
|
#
|
64
|
-
def
|
65
|
-
|
58
|
+
def render_with_context(_context)
|
59
|
+
{}
|
66
60
|
end
|
67
61
|
|
68
62
|
def collection?
|
@@ -71,6 +65,13 @@ module Halitosis
|
|
71
65
|
|
72
66
|
protected
|
73
67
|
|
68
|
+
# Build a new context instance using this serializer instance
|
69
|
+
#
|
70
|
+
# @return [Halitosis::Context] the context instance
|
71
|
+
def build_context(options = {})
|
72
|
+
Context.new(self, HashUtil.deep_merge(@options, options))
|
73
|
+
end
|
74
|
+
|
74
75
|
# Allow included modules to decorate rendered hash
|
75
76
|
#
|
76
77
|
# @param key [Symbol] the key (e.g. `embedded`, `links`)
|
@@ -78,9 +79,9 @@ module Halitosis
|
|
78
79
|
#
|
79
80
|
# @return [Hash] the decorated hash
|
80
81
|
#
|
81
|
-
def decorate_render(key, result)
|
82
|
+
def decorate_render(key, context, result)
|
82
83
|
result.tap do
|
83
|
-
value = send(key)
|
84
|
+
value = send(key, context)
|
84
85
|
|
85
86
|
result[:"_#{key}"] = value if value.any?
|
86
87
|
end
|
@@ -93,11 +94,11 @@ module Halitosis
|
|
93
94
|
#
|
94
95
|
# @return [Hash] the result
|
95
96
|
#
|
96
|
-
def render_fields(type)
|
97
|
-
fields = self.class.fields.
|
97
|
+
def render_fields(type, context)
|
98
|
+
fields = self.class.fields.for_type(type)
|
98
99
|
|
99
100
|
fields.each_with_object({}) do |field, result|
|
100
|
-
next unless field.enabled?(
|
101
|
+
next unless field.enabled?(context)
|
101
102
|
|
102
103
|
yield field, result
|
103
104
|
end
|
@@ -108,15 +109,10 @@ module Halitosis
|
|
108
109
|
#
|
109
110
|
# @return [nil, Hash] the rendered child
|
110
111
|
#
|
111
|
-
def render_child(child, opts)
|
112
|
-
return unless child.class.included_modules.include?(Halitosis)
|
113
|
-
|
114
|
-
child.options[:include] ||= {}
|
115
|
-
child.options[:include] = child.options[:include].merge(opts)
|
116
|
-
|
117
|
-
child.options[:parent] = self
|
112
|
+
def render_child(child, context, opts)
|
113
|
+
return unless child.class.included_modules.include?(Halitosis::Base)
|
118
114
|
|
119
|
-
child.
|
115
|
+
child.render_with_context child.build_context(parent: context, include: opts)
|
120
116
|
end
|
121
117
|
end
|
122
118
|
end
|
@@ -3,6 +3,17 @@
|
|
3
3
|
module Halitosis
|
4
4
|
module Collection
|
5
5
|
class Field < Halitosis::Field
|
6
|
+
# @return [true] if nothing is raised
|
7
|
+
#
|
8
|
+
# @raise [Halitosis::InvalidField] if the definition is invalid
|
9
|
+
#
|
10
|
+
def validate
|
11
|
+
super
|
12
|
+
|
13
|
+
return true if procedure
|
14
|
+
|
15
|
+
raise InvalidField, "Collection #{name} must be defined with a proc"
|
16
|
+
end
|
6
17
|
end
|
7
18
|
end
|
8
19
|
end
|
data/lib/halitosis/collection.rb
CHANGED
@@ -15,8 +15,6 @@ module Halitosis
|
|
15
15
|
base.send :include, InstanceMethods
|
16
16
|
|
17
17
|
base.send :attr_reader, :collection
|
18
|
-
|
19
|
-
base.class.send :attr_accessor, :collection_name
|
20
18
|
end
|
21
19
|
|
22
20
|
module ClassMethods
|
@@ -25,9 +23,9 @@ module Halitosis
|
|
25
23
|
# @return [Module] self
|
26
24
|
#
|
27
25
|
def define_collection(name, options = {}, &procedure)
|
28
|
-
raise InvalidCollection, "#{self.name} collection is already defined" if fields.
|
26
|
+
raise InvalidCollection, "#{self.name || Collection.name} collection is already defined" if fields.for_type(Field).any?
|
29
27
|
|
30
|
-
self.
|
28
|
+
self.resource_type = name.to_s
|
31
29
|
|
32
30
|
alias_method name, :collection
|
33
31
|
|
@@ -39,7 +37,22 @@ module Halitosis
|
|
39
37
|
end
|
40
38
|
|
41
39
|
def collection_field
|
42
|
-
fields
|
40
|
+
fields.for_type(Field).last || raise(InvalidCollection, "#{name || Collection.name} collection is not defined")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Provide an alias for root_link
|
44
|
+
def link(*, **, &)
|
45
|
+
root_link(*, **, &)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Provide an alias for root_meta
|
49
|
+
def meta(*, **, &)
|
50
|
+
root_meta(*, **, &)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Provide an alias for root_permission
|
54
|
+
def permission(*, **, &)
|
55
|
+
root_permission(*, **, &)
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
@@ -50,47 +63,47 @@ module Halitosis
|
|
50
63
|
#
|
51
64
|
def initialize(collection, **)
|
52
65
|
@collection = collection
|
66
|
+
@collection_field = self.class.collection_field
|
53
67
|
|
54
68
|
super(**)
|
55
69
|
end
|
56
70
|
|
57
|
-
# @return [Hash] the rendered hash with collection,
|
71
|
+
# @return [Hash, Array] the rendered hash with collection, as an array or a hash under a key
|
58
72
|
#
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
73
|
+
def render_with_context(context)
|
74
|
+
if (include_root = context.fetch(:include_root) { context.depth.zero? })
|
75
|
+
{
|
76
|
+
root_name(include_root) => render_collection_field(context)
|
77
|
+
}.merge(super)
|
63
78
|
else
|
64
|
-
render_collection_field(
|
79
|
+
render_collection_field(context)
|
65
80
|
end
|
66
81
|
end
|
67
82
|
|
68
|
-
# @return [Hash] collection from fields
|
69
|
-
#
|
70
|
-
def render_collection_field(field)
|
71
|
-
value = instance_eval(&field.procedure)
|
72
|
-
value.map { |child| render_child(child, collection_opts) }
|
73
|
-
end
|
74
|
-
|
75
83
|
def collection?
|
76
84
|
true
|
77
85
|
end
|
78
86
|
|
79
87
|
private
|
80
88
|
|
81
|
-
|
82
|
-
|
83
|
-
# @return [Hash]
|
89
|
+
attr_reader :collection_field
|
90
|
+
|
91
|
+
# @return [Hash] collection from fields
|
84
92
|
#
|
85
|
-
def
|
86
|
-
|
93
|
+
def render_collection_field(context)
|
94
|
+
value = collection_field.value(context)
|
87
95
|
|
88
|
-
|
96
|
+
return render_child(value, context, context.include_options) if value.is_a?(Halitosis::Collection)
|
97
|
+
|
98
|
+
value.reject { |child| child.is_a?(Halitosis::Collection) } # Skip nested collections in array
|
99
|
+
.map { |child| render_child(child, context, context.include_options) }
|
100
|
+
.compact
|
101
|
+
end
|
89
102
|
|
90
|
-
|
91
|
-
|
103
|
+
def root_name(include_root)
|
104
|
+
return include_root.to_sym if include_root.is_a?(String) || include_root.is_a?(Symbol)
|
92
105
|
|
93
|
-
|
106
|
+
self.class.resource_type.to_sym
|
94
107
|
end
|
95
108
|
end
|
96
109
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Halitosis
|
2
|
+
class Context
|
3
|
+
# @param instance [Halitosis::Base] the serializer instance
|
4
|
+
# @param options [Hash] hash of options
|
5
|
+
def initialize(instance, options = {})
|
6
|
+
@instance = instance
|
7
|
+
@options = HashUtil.symbolize_hash(options).freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
### Instance ###
|
11
|
+
|
12
|
+
# Evaluate guard procedure or method on the serializer instance
|
13
|
+
#
|
14
|
+
def call_instance(guard)
|
15
|
+
case guard
|
16
|
+
when Proc
|
17
|
+
instance.instance_exec(self, &guard)
|
18
|
+
when Symbol, String
|
19
|
+
instance.send(guard)
|
20
|
+
else
|
21
|
+
guard
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
### Options ###
|
26
|
+
|
27
|
+
def fetch(...)
|
28
|
+
options.fetch(...)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] hash of options with top level string keys
|
32
|
+
#
|
33
|
+
def include_options
|
34
|
+
@include_options ||= HashUtil.hasherize_include_option(options[:include] || {})
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [nil, Halitosis::Context] the parent context, if this instance is an
|
38
|
+
# embedded child
|
39
|
+
#
|
40
|
+
def parent
|
41
|
+
options.fetch(:parent, nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer] the depth at which this serializer is embedded
|
45
|
+
#
|
46
|
+
def depth
|
47
|
+
@depth ||= parent ? parent.depth + 1 : 0
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :instance, :options
|
53
|
+
end
|
54
|
+
end
|
data/lib/halitosis/errors.rb
CHANGED
@@ -3,9 +3,22 @@
|
|
3
3
|
module Halitosis
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
+
### Configuration Errors ###
|
7
|
+
|
6
8
|
class InvalidCollection < StandardError; end
|
7
9
|
|
8
10
|
class InvalidField < StandardError; end
|
9
11
|
|
10
12
|
class InvalidResource < StandardError; end
|
13
|
+
|
14
|
+
### Rendering Errors ###
|
15
|
+
|
16
|
+
class InvalidQueryParameter < Error
|
17
|
+
def initialize(message, parameter)
|
18
|
+
@parameter = parameter
|
19
|
+
super(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :parameter
|
23
|
+
end
|
11
24
|
end
|
data/lib/halitosis/field.rb
CHANGED
@@ -7,8 +7,6 @@ module Halitosis
|
|
7
7
|
class Field
|
8
8
|
attr_reader :name, :options
|
9
9
|
|
10
|
-
attr_accessor :procedure
|
11
|
-
|
12
10
|
# Construct a new Field instance
|
13
11
|
#
|
14
12
|
# @param name [Symbol, String] Field name
|
@@ -18,27 +16,25 @@ module Halitosis
|
|
18
16
|
#
|
19
17
|
def initialize(name, options, procedure)
|
20
18
|
@name = name.to_sym
|
21
|
-
@options = Halitosis::HashUtil.
|
19
|
+
@options = Halitosis::HashUtil.symbolize_hash(options)
|
22
20
|
@procedure = procedure
|
23
21
|
end
|
24
22
|
|
25
|
-
# @param
|
23
|
+
# @param context [Halitosis::Context] the serializer instance with which to evaluate
|
26
24
|
# the stored procedure
|
27
25
|
#
|
28
|
-
def value(
|
29
|
-
options.fetch(:value)
|
30
|
-
procedure ? instance.instance_eval(&procedure) : instance.send(name)
|
31
|
-
end
|
26
|
+
def value(context)
|
27
|
+
options.fetch(:value) { context.call_instance(procedure || name) }
|
32
28
|
end
|
33
29
|
|
34
30
|
# @return [true, false] whether this Field should be included based on
|
35
31
|
# its conditional guard, if any
|
36
32
|
#
|
37
|
-
def enabled?(
|
33
|
+
def enabled?(context)
|
38
34
|
if options.key?(:if)
|
39
|
-
!!
|
35
|
+
!!context.call_instance(options.fetch(:if))
|
40
36
|
elsif options.key?(:unless)
|
41
|
-
!
|
37
|
+
!context.call_instance(options.fetch(:unless))
|
42
38
|
else
|
43
39
|
true
|
44
40
|
end
|
@@ -57,17 +53,6 @@ module Halitosis
|
|
57
53
|
|
58
54
|
private
|
59
55
|
|
60
|
-
|
61
|
-
#
|
62
|
-
def eval_guard(instance, guard)
|
63
|
-
case guard
|
64
|
-
when Proc
|
65
|
-
instance.instance_eval(&guard)
|
66
|
-
when Symbol, String
|
67
|
-
instance.send(guard)
|
68
|
-
else
|
69
|
-
guard
|
70
|
-
end
|
71
|
-
end
|
56
|
+
attr_reader :procedure
|
72
57
|
end
|
73
58
|
end
|
data/lib/halitosis/fields.rb
CHANGED
data/lib/halitosis/hash_util.rb
CHANGED
@@ -4,23 +4,42 @@ module Halitosis
|
|
4
4
|
module HashUtil
|
5
5
|
module_function
|
6
6
|
|
7
|
-
# Transform
|
7
|
+
# Transform include params into a hash
|
8
8
|
#
|
9
|
-
# @param
|
9
|
+
# @param object [Hash, Array, String]
|
10
10
|
#
|
11
11
|
# @return [Hash]
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
def hasherize_include_option(object)
|
13
|
+
case object
|
14
|
+
when Hash
|
15
|
+
object.transform_keys(&:to_s)
|
16
|
+
when String, Symbol
|
17
|
+
object.to_s.split(",").inject({}) do |output, key|
|
17
18
|
f, value = key.split(".", 2)
|
18
|
-
output
|
19
|
+
deep_merge(output, f => value ? hasherize_include_option(value) : {})
|
19
20
|
end
|
20
21
|
when Array
|
21
|
-
|
22
|
+
object.inject({}) do |output, value|
|
23
|
+
deep_merge(output, hasherize_include_option(value))
|
24
|
+
end
|
22
25
|
else
|
23
|
-
|
26
|
+
object
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Deep merge two hashes
|
31
|
+
#
|
32
|
+
# @param hash [Hash]
|
33
|
+
# @param other_hash [Hash]
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
def deep_merge(hash, other_hash)
|
37
|
+
hash.merge(other_hash) do |key, this_val, other_val|
|
38
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
39
|
+
deep_merge(this_val, other_val)
|
40
|
+
else
|
41
|
+
other_val
|
42
|
+
end
|
24
43
|
end
|
25
44
|
end
|
26
45
|
|
@@ -30,9 +49,9 @@ module Halitosis
|
|
30
49
|
#
|
31
50
|
# @return [Hash]
|
32
51
|
#
|
33
|
-
def
|
52
|
+
def symbolize_hash(hash)
|
34
53
|
if hash.respond_to?(:transform_keys)
|
35
|
-
hash.transform_keys(&:to_sym).transform_values(&method(:
|
54
|
+
hash.transform_keys(&:to_sym).transform_values(&method(:symbolize_hash))
|
36
55
|
else
|
37
56
|
hash
|
38
57
|
end
|