clearly-query 0.3.1.pre
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/.codeclimate.yml +8 -0
- data/.gitignore +42 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +43 -0
- data/Gemfile +7 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +102 -0
- data/Rakefile +7 -0
- data/SPEC.md +101 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/yard +16 -0
- data/clearly-query.gemspec +33 -0
- data/lib/clearly/query.rb +22 -0
- data/lib/clearly/query/cleaner.rb +63 -0
- data/lib/clearly/query/compose/comparison.rb +102 -0
- data/lib/clearly/query/compose/conditions.rb +215 -0
- data/lib/clearly/query/compose/core.rb +75 -0
- data/lib/clearly/query/compose/custom.rb +268 -0
- data/lib/clearly/query/compose/range.rb +114 -0
- data/lib/clearly/query/compose/special.rb +24 -0
- data/lib/clearly/query/compose/subset.rb +115 -0
- data/lib/clearly/query/composer.rb +269 -0
- data/lib/clearly/query/definition.rb +165 -0
- data/lib/clearly/query/errors.rb +27 -0
- data/lib/clearly/query/graph.rb +63 -0
- data/lib/clearly/query/helper.rb +50 -0
- data/lib/clearly/query/validate.rb +296 -0
- data/lib/clearly/query/version.rb +8 -0
- data/spec/lib/clearly/query/cleaner_spec.rb +42 -0
- data/spec/lib/clearly/query/compose/custom_spec.rb +77 -0
- data/spec/lib/clearly/query/composer_query_spec.rb +50 -0
- data/spec/lib/clearly/query/composer_spec.rb +422 -0
- data/spec/lib/clearly/query/definition_spec.rb +23 -0
- data/spec/lib/clearly/query/graph_spec.rb +81 -0
- data/spec/lib/clearly/query/helper_spec.rb +17 -0
- data/spec/lib/clearly/query/version_spec.rb +7 -0
- data/spec/spec_helper.rb +89 -0
- data/spec/support/db/migrate/001_db_create.rb +62 -0
- data/spec/support/models/customer.rb +63 -0
- data/spec/support/models/order.rb +66 -0
- data/spec/support/models/part.rb +63 -0
- data/spec/support/models/product.rb +67 -0
- data/spec/support/shared_setup.rb +13 -0
- data/tmp/.gitkeep +0 -0
- metadata +263 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Clearly
|
|
2
|
+
module Query
|
|
3
|
+
|
|
4
|
+
# Generic error from Clearly Query
|
|
5
|
+
class QueryArgumentError < ArgumentError
|
|
6
|
+
|
|
7
|
+
# @return [Hash] partial filter hash
|
|
8
|
+
attr_reader :filter_segment
|
|
9
|
+
|
|
10
|
+
# Create a Filter Argument Error
|
|
11
|
+
# @param [String] message
|
|
12
|
+
# @param [Hash] filter_segment
|
|
13
|
+
# @return [QueryArgumentError]
|
|
14
|
+
def initialize(message = nil, filter_segment = nil)
|
|
15
|
+
@message = message
|
|
16
|
+
@filter_segment = filter_segment
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Show a string representation of this error
|
|
21
|
+
# @return [String]
|
|
22
|
+
def to_s
|
|
23
|
+
@message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Clearly
|
|
2
|
+
module Query
|
|
3
|
+
|
|
4
|
+
# Stores a graph and provides methods to operate on the graph.
|
|
5
|
+
# Graph nodes are a hash, and one special key contains an array of child nodes.
|
|
6
|
+
class Graph
|
|
7
|
+
|
|
8
|
+
# root node
|
|
9
|
+
attr_reader :root_node
|
|
10
|
+
|
|
11
|
+
# name of the hash key that holds the child nodes
|
|
12
|
+
attr_reader :child_key
|
|
13
|
+
|
|
14
|
+
# Create a new Graph.
|
|
15
|
+
# @param [Array] root_node
|
|
16
|
+
# @param [Symbol] child_key
|
|
17
|
+
# @return [Clearly::Query::DepthFirstSearch]
|
|
18
|
+
def initialize(root_node, child_key)
|
|
19
|
+
@root_node = root_node
|
|
20
|
+
@child_key = child_key
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# build an array that contains paths from the root to all leaves
|
|
25
|
+
# @return [Array] paths from root to leaf
|
|
26
|
+
def branches
|
|
27
|
+
@discovered_nodes = []
|
|
28
|
+
@paths = []
|
|
29
|
+
traverse_branches(@root_node, nil)
|
|
30
|
+
@paths
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def traverse_branches(current_node, current_path)
|
|
36
|
+
child_nodes = current_node.include?(@child_key) ? current_node[@child_key] : []
|
|
37
|
+
|
|
38
|
+
current_node_no_children = current_node.dup.except(@child_key)
|
|
39
|
+
|
|
40
|
+
@discovered_nodes.push(current_node_no_children)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if child_nodes.size > 0
|
|
44
|
+
current_node[@child_key].each do |node|
|
|
45
|
+
child_node_no_children = node.dup.except(@child_key)
|
|
46
|
+
|
|
47
|
+
unless @discovered_nodes.include?(child_node_no_children)
|
|
48
|
+
node_path = current_path.nil? ? [] : current_path.dup
|
|
49
|
+
node_path.push(current_node_no_children)
|
|
50
|
+
traverse_branches(node, node_path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
node_path = current_path.nil? ? [] : current_path.dup
|
|
56
|
+
node_path.push(current_node_no_children)
|
|
57
|
+
@paths.push(node_path)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Clearly
|
|
2
|
+
module Query
|
|
3
|
+
|
|
4
|
+
# Utility methods for working with Arel.
|
|
5
|
+
class Helper
|
|
6
|
+
class << self
|
|
7
|
+
|
|
8
|
+
# Concatenate one or more strings
|
|
9
|
+
# @param [Array<String>] args strings to concatenate
|
|
10
|
+
# @return [Arel::Nodes::Node]
|
|
11
|
+
def string_concat(*args)
|
|
12
|
+
adapter = ActiveRecord::Base.connection.adapter_name.underscore.downcase
|
|
13
|
+
|
|
14
|
+
case adapter
|
|
15
|
+
when 'mysql'
|
|
16
|
+
Arel::Nodes::NamedFunction.new('concat', *args)
|
|
17
|
+
when 'sqlserver'
|
|
18
|
+
string_concat_infix('+', *args)
|
|
19
|
+
when 'postgres'
|
|
20
|
+
when 'sq_lite'
|
|
21
|
+
string_concat_infix('||', *args)
|
|
22
|
+
else
|
|
23
|
+
fail ArgumentError, "unsupported database adapter '#{adapter}'"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Concatenate strings using an operator
|
|
28
|
+
# @param [Object] operator infix operator
|
|
29
|
+
# @param [Array<String>] args strings to concatenate
|
|
30
|
+
# @return [Arel::Nodes::Node]
|
|
31
|
+
def string_concat_infix(operator, *args)
|
|
32
|
+
if args.blank? || args.size < 2
|
|
33
|
+
fail ArgumentError, "string concatenation requires operator and two or more arguments, given '#{args.size}'"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result = Arel::Nodes::InfixOperation.new(operator, args[0], args[1])
|
|
37
|
+
|
|
38
|
+
if args.size > 2
|
|
39
|
+
args.drop(2).each do |a|
|
|
40
|
+
result = Arel::Nodes::InfixOperation.new(operator, result, a)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
module Clearly
|
|
2
|
+
module Query
|
|
3
|
+
|
|
4
|
+
# Provides common validations for composing queries.
|
|
5
|
+
module Validate
|
|
6
|
+
|
|
7
|
+
# Validate query, table, and column values.
|
|
8
|
+
# @param [Arel::Query] query
|
|
9
|
+
# @param [Arel::Table] table
|
|
10
|
+
# @param [Symbol] column_name
|
|
11
|
+
# @param [Array<Symbol>] allowed
|
|
12
|
+
# @return [void]
|
|
13
|
+
def validate_query_table_column(query, table, column_name, allowed)
|
|
14
|
+
validate_query(query)
|
|
15
|
+
validate_table(table)
|
|
16
|
+
validate_name(column_name, allowed)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Validate table and column values.
|
|
20
|
+
# @param [Arel::Table] table
|
|
21
|
+
# @param [Symbol] column_name
|
|
22
|
+
# @param [Array<Symbol>] allowed
|
|
23
|
+
# @return [void]
|
|
24
|
+
def validate_table_column(table, column_name, allowed)
|
|
25
|
+
validate_table(table)
|
|
26
|
+
validate_name(column_name, allowed)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Validate model association.
|
|
30
|
+
# @param [ActiveRecord::Base] model
|
|
31
|
+
# @param [Array<ActiveRecord::Base>] models_allowed
|
|
32
|
+
# @return [void]
|
|
33
|
+
def validate_association(model, models_allowed)
|
|
34
|
+
validate_model(model)
|
|
35
|
+
validate_not_blank(models_allowed)
|
|
36
|
+
validate_array(models_allowed)
|
|
37
|
+
|
|
38
|
+
fail Clearly::Query::QueryArgumentError, "models allowed must be an Array, got '#{models_allowed}'" unless models_allowed.is_a?(Array)
|
|
39
|
+
fail Clearly::Query::QueryArgumentError, "model must be in '#{models_allowed}', got '#{model}'" unless models_allowed.include?(model)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Validate query and hash values.
|
|
43
|
+
# @param [ActiveRecord::Relation] query
|
|
44
|
+
# @param [Hash] hash
|
|
45
|
+
# @return [void]
|
|
46
|
+
def validate_query_hash(query, hash)
|
|
47
|
+
validate_query(query)
|
|
48
|
+
validate_hash(hash)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Validate table value.
|
|
52
|
+
# @param [Arel::Table] table
|
|
53
|
+
# @raise [FilterArgumentError] if table is not an Arel::Table
|
|
54
|
+
# @return [void]
|
|
55
|
+
def validate_table(table)
|
|
56
|
+
fail Clearly::Query::QueryArgumentError, "table must be Arel::Table, got '#{table.class}'" unless table.is_a?(Arel::Table)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Validate table value.
|
|
60
|
+
# @param [ActiveRecord::Relation] query
|
|
61
|
+
# @raise [FilterArgumentError] if query is not an Arel::Query
|
|
62
|
+
# @return [void]
|
|
63
|
+
def validate_query(query)
|
|
64
|
+
fail Clearly::Query::QueryArgumentError, "query must be ActiveRecord::Relation, got '#{query.class}'" unless query.is_a?(ActiveRecord::Relation)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Validate condition value.
|
|
68
|
+
# @param [Arel::Nodes::Node] condition
|
|
69
|
+
# @raise [FilterArgumentError] if condition is not an Arel::Nodes::Node
|
|
70
|
+
# @return [void]
|
|
71
|
+
def validate_condition(condition)
|
|
72
|
+
if !condition.is_a?(Arel::Nodes::Node) && !condition.is_a?(String)
|
|
73
|
+
fail Clearly::Query::QueryArgumentError, "condition must be Arel::Nodes::Node or String, got '#{condition}'"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Validate value is a node or attribute
|
|
78
|
+
# @param [Arel::Nodes::Node, Arel::Attributes::Attribute, String] value
|
|
79
|
+
# @return [void]
|
|
80
|
+
def validate_node_or_attribute(value)
|
|
81
|
+
check = value.is_a?(Arel::Nodes::Node) || value.is_a?(String) || value.is_a?(Arel::Attributes::Attribute) || value.is_a?(Symbol)
|
|
82
|
+
fail Clearly::Query::QueryArgumentError, "value must be Arel::Nodes::Node or String or Symbol or Arel::Attributes::Attribute, got '#{value}'" unless check
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validate name value.
|
|
86
|
+
# @param [Symbol] name
|
|
87
|
+
# @param [Array<Symbol>] allowed
|
|
88
|
+
# @raise [FilterArgumentError] if name is not a symbol in allowed
|
|
89
|
+
# @return [void]
|
|
90
|
+
def validate_name(name, allowed)
|
|
91
|
+
validate_not_blank(name)
|
|
92
|
+
fail Clearly::Query::QueryArgumentError, "name must be a symbol, got '#{name}'" unless name.is_a?(Symbol)
|
|
93
|
+
fail Clearly::Query::QueryArgumentError, "allowed must be an Array, got '#{allowed}'" unless allowed.is_a?(Array)
|
|
94
|
+
fail Clearly::Query::QueryArgumentError, "name must be in '#{allowed}', got '#{name}'" unless allowed.include?(name)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Validate model value.
|
|
98
|
+
# @param [ActiveRecord::Base] model
|
|
99
|
+
# @raise [FilterArgumentError] if model is not an ActiveRecord::Base
|
|
100
|
+
# @return [void]
|
|
101
|
+
def validate_model(model)
|
|
102
|
+
validate_not_blank(model)
|
|
103
|
+
fail Clearly::Query::QueryArgumentError, "model must be an ActiveRecord::Base, got '#{model.base_class}'" unless model < ActiveRecord::Base
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Validate an array.
|
|
107
|
+
# @param [Array, Arel::SelectManager] value
|
|
108
|
+
# @raise [FilterArgumentError] if value is not a valid Array.
|
|
109
|
+
# @return [void]
|
|
110
|
+
def validate_array(value)
|
|
111
|
+
fail Clearly::Query::QueryArgumentError, "value must be an Array or Arel::SelectManager, got '#{value.class}'" unless value.is_a?(Array) || value.is_a?(Arel::SelectManager)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Validate array items. Do not validate if value is not an Array.
|
|
115
|
+
# @param [Array] value
|
|
116
|
+
# @raise [FilterArgumentError] if Array contents are not valid.
|
|
117
|
+
# @return [void]
|
|
118
|
+
def validate_array_items(value)
|
|
119
|
+
# must be a collection of items
|
|
120
|
+
if !value.respond_to?(:each) || !value.respond_to?(:all?) || !value.respond_to?(:any?) || !value.respond_to?(:count)
|
|
121
|
+
fail Clearly::Query::QueryArgumentError, "must be a collection of items, got '#{value.class}'"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# if there are no items, let it through
|
|
125
|
+
if value.count > 0
|
|
126
|
+
# all items must be the same type (or a subclass). Assume the first item is the correct type.
|
|
127
|
+
type_compare_item = value[0].class
|
|
128
|
+
type_compare = value.all? do |item|
|
|
129
|
+
is_same_class = item.is_a?(type_compare_item)
|
|
130
|
+
item_class = item.class
|
|
131
|
+
is_same_class ? true : (item_class <= Arel::Nodes::Node && type_compare_item <= Arel::Nodes::Node)
|
|
132
|
+
end
|
|
133
|
+
fail Clearly::Query::QueryArgumentError, "array values must be a single consistent type, got '#{value.map { |v| v.class.name }.join(', ')}'" unless type_compare
|
|
134
|
+
|
|
135
|
+
# restrict length of strings
|
|
136
|
+
if type_compare_item.is_a?(String)
|
|
137
|
+
max_string_length = 120
|
|
138
|
+
string_length = value.all? { |item| item.size <= max_string_length }
|
|
139
|
+
fail Clearly::Query::QueryArgumentError, "array values that are strings must be '#{max_string_length}' characters or less" unless string_length
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# array contents cannot be Arrays or Hashes
|
|
143
|
+
array_check = value.any? { |item| item.is_a?(Array) }
|
|
144
|
+
fail Clearly::Query::QueryArgumentError, 'array values cannot be arrays' if array_check
|
|
145
|
+
|
|
146
|
+
hash_check = value.any? { |item| item.is_a?(Hash) }
|
|
147
|
+
fail Clearly::Query::QueryArgumentError, 'array values cannot be hashes' if hash_check
|
|
148
|
+
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Validate a hash.
|
|
153
|
+
# @param [Array] value
|
|
154
|
+
# @raise [FilterArgumentError] if value is not a valid Hash.
|
|
155
|
+
# @return [void]
|
|
156
|
+
def validate_hash(value)
|
|
157
|
+
validate_not_blank(value)
|
|
158
|
+
fail Clearly::Query::QueryArgumentError, "value must be a Hash, got '#{value}'" unless value.is_a?(Hash)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Validate a symbol.
|
|
162
|
+
# @param [Symbol] value
|
|
163
|
+
# @raise [FilterArgumentError] if value is not a Symbol.
|
|
164
|
+
# @return [void]
|
|
165
|
+
def validate_symbol(value)
|
|
166
|
+
validate_not_blank(value)
|
|
167
|
+
fail Clearly::Query::QueryArgumentError, "value must be a Symbol, got '#{value}'" unless value.is_a?(Symbol)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Validate value is not blank
|
|
171
|
+
# @param [Object] value
|
|
172
|
+
# @return [void]
|
|
173
|
+
def validate_not_blank(value)
|
|
174
|
+
fail Clearly::Query::QueryArgumentError, "value must not be empty, got '#{value}'" if value.blank?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Validate value is a boolean
|
|
178
|
+
# @param [Boolean] value
|
|
179
|
+
# @return [void]
|
|
180
|
+
def validate_boolean(value)
|
|
181
|
+
fail Clearly::Query::QueryArgumentError, "value must be a boolean, got '#{value}'" if !value.is_a?(TrueClass) && !value.is_a?(FalseClass)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Escape wildcards in like value..
|
|
185
|
+
# @param [String] value
|
|
186
|
+
# @return [String] sanitized value
|
|
187
|
+
def sanitize_like_value(value)
|
|
188
|
+
value.gsub(/[\\_%\|]/) { |x| "\\#{x}" }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Escape meta-characters in SIMILAR TO value.
|
|
192
|
+
# see http://www.postgresql.org/docs/9.3/static/functions-matching.html
|
|
193
|
+
# @param [String] value
|
|
194
|
+
# @return [String] sanitized value
|
|
195
|
+
def sanitize_similar_to_value(value)
|
|
196
|
+
value.gsub(/[\\_%\|\*\+\?\{\}\(\)\[\]]/) { |x| "\\#{x}" }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Create LIKE syntax.
|
|
200
|
+
# @param [String] value
|
|
201
|
+
# @param [Hash] options
|
|
202
|
+
# @return [String]
|
|
203
|
+
def like_syntax(value, options = {start: false, end: false})
|
|
204
|
+
"#{options[:start] ? '%' : ''}#{sanitize_like_value(value)}#{options[:end] ? '%' : ''}"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# validate an integer
|
|
208
|
+
# @param [Object] value
|
|
209
|
+
# @param [Integer] min
|
|
210
|
+
# @param [Integer] max
|
|
211
|
+
# @return [void]
|
|
212
|
+
def validate_integer(value, min = nil, max = nil)
|
|
213
|
+
validate_not_blank(value)
|
|
214
|
+
fail Clearly::Query::QueryArgumentError, "value must be an integer, got '#{value}'" if value != value.to_i
|
|
215
|
+
|
|
216
|
+
value_i = value.to_i
|
|
217
|
+
|
|
218
|
+
fail Clearly::Query::QueryArgumentError, "value must be '#{min}' or greater, got '#{value_i}'" if !min.blank? && value_i < min
|
|
219
|
+
fail Clearly::Query::QueryArgumentError, "value must be '#{max}' or less, got '#{value_i}'" if !max.blank? && value_i > max
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Check that value is a float.
|
|
223
|
+
# @param [Object] value
|
|
224
|
+
# @raise [FilterArgumentError] if value is not a float
|
|
225
|
+
# @return [void]
|
|
226
|
+
def validate_float(value)
|
|
227
|
+
validate_not_blank(value)
|
|
228
|
+
|
|
229
|
+
filtered = value.to_s.tr('^0-9.', '')
|
|
230
|
+
fail Clearly::Query::QueryArgumentError, "value must be a float, got '#{filtered}'" if filtered != value
|
|
231
|
+
fail Clearly::Query::QueryArgumentError, "value must be a float after conversion, got '#{filtered}'" if filtered != value.to_f
|
|
232
|
+
|
|
233
|
+
value_f = filtered.to_f
|
|
234
|
+
fail Clearly::Query::QueryArgumentError, "value must be greater than 0, got '#{value_f}'" if value_f <= 0
|
|
235
|
+
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Validate definition instance
|
|
239
|
+
# @param [Clearly::Query::Definition] value
|
|
240
|
+
# @return [void]
|
|
241
|
+
def validate_definition_instance(value)
|
|
242
|
+
validate_not_blank(value)
|
|
243
|
+
fail Clearly::Query::QueryArgumentError, "value must be a model definition, got '#{value.class}'" unless value.is_a?(Clearly::Query::Definition)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Validate definition specification
|
|
247
|
+
# @param [Hash] value
|
|
248
|
+
# @return [void]
|
|
249
|
+
def validate_definition(value)
|
|
250
|
+
validate_hash(value)
|
|
251
|
+
|
|
252
|
+
# fields
|
|
253
|
+
validate_hash(value[:fields])
|
|
254
|
+
|
|
255
|
+
validate_not_blank(value[:fields][:valid])
|
|
256
|
+
validate_array(value[:fields][:valid])
|
|
257
|
+
validate_array_items(value[:fields][:valid])
|
|
258
|
+
|
|
259
|
+
validate_not_blank(value[:fields][:text])
|
|
260
|
+
validate_array(value[:fields][:text])
|
|
261
|
+
validate_array_items(value[:fields][:text])
|
|
262
|
+
|
|
263
|
+
validate_not_blank(value[:fields][:mappings])
|
|
264
|
+
validate_array(value[:fields][:mappings])
|
|
265
|
+
|
|
266
|
+
value[:fields][:mappings].each do |mapping|
|
|
267
|
+
validate_hash(mapping)
|
|
268
|
+
validate_symbol(mapping[:name])
|
|
269
|
+
validate_not_blank(mapping[:value])
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# associations
|
|
273
|
+
validate_spec_association(value[:associations])
|
|
274
|
+
|
|
275
|
+
# defaults
|
|
276
|
+
validate_hash(value[:defaults])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Validate association specification
|
|
280
|
+
# @param [Array] value
|
|
281
|
+
# @return [void]
|
|
282
|
+
def validate_spec_association(value)
|
|
283
|
+
validate_array(value)
|
|
284
|
+
|
|
285
|
+
value.each do |association|
|
|
286
|
+
validate_hash(association)
|
|
287
|
+
validate_not_blank(association[:join])
|
|
288
|
+
validate_not_blank(association[:on])
|
|
289
|
+
validate_boolean(association[:available])
|
|
290
|
+
validate_spec_association(association[:associations]) if association.include?(:associations)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|