dbee 2.1.1 → 3.0.0
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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +11 -0
- data/README.md +133 -58
- data/dbee.gemspec +5 -2
- data/lib/dbee.rb +9 -10
- data/lib/dbee/base.rb +7 -49
- data/lib/dbee/dsl_schema_builder.rb +86 -0
- data/lib/dbee/key_chain.rb +11 -0
- data/lib/dbee/model.rb +50 -38
- data/lib/dbee/model/relationships.rb +24 -0
- data/lib/dbee/model/relationships/basic.rb +47 -0
- data/lib/dbee/query.rb +4 -0
- data/lib/dbee/query/field.rb +1 -6
- data/lib/dbee/schema.rb +66 -0
- data/lib/dbee/schema_creator.rb +107 -0
- data/lib/dbee/schema_from_tree_based_model.rb +47 -0
- data/lib/dbee/util/make_keyed_by.rb +50 -0
- data/lib/dbee/version.rb +1 -1
- data/spec/dbee/base_spec.rb +9 -72
- data/spec/dbee/constant_resolver_spec.rb +17 -12
- data/spec/dbee/dsl_schema_builder_spec.rb +106 -0
- data/spec/dbee/key_chain_spec.rb +24 -0
- data/spec/dbee/model/constraints_spec.rb +6 -7
- data/spec/dbee/model_spec.rb +62 -59
- data/spec/dbee/query/filters_spec.rb +16 -17
- data/spec/dbee/query_spec.rb +55 -54
- data/spec/dbee/schema_creator_spec.rb +163 -0
- data/spec/dbee/schema_from_tree_based_model_spec.rb +31 -0
- data/spec/dbee/schema_spec.rb +62 -0
- data/spec/dbee_spec.rb +17 -37
- data/spec/fixtures/models.yaml +254 -56
- data/spec/spec_helper.rb +7 -0
- metadata +66 -9
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Dbee
|
11
|
+
# Builds a Dbee::Schema given a Dbee::Base (DSL) model. Note that this class
|
12
|
+
# does not exist in the "Dsl" module as it is not used for defining the DSL
|
13
|
+
# itself.
|
14
|
+
class DslSchemaBuilder # :nodoc:
|
15
|
+
attr_reader :dsl_model, :key_chain
|
16
|
+
|
17
|
+
def initialize(dsl_model, key_chain)
|
18
|
+
@dsl_model = dsl_model || ArgumentError('dsl_model is required')
|
19
|
+
@key_chain = key_chain || ArgumentError('key_chain is required')
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_schema
|
25
|
+
schema_spec = { dsl_model.inflected_class_name => model_config(dsl_model) }
|
26
|
+
|
27
|
+
ancestor_paths(key_chain).each do |key_path|
|
28
|
+
start_model = dsl_model
|
29
|
+
|
30
|
+
key_path.ancestor_names.each do |association_name|
|
31
|
+
start_model = append_model_and_relationship(schema_spec, start_model, association_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Schema.new(schema_spec)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def model_config(dsl_model) #:nodoc:
|
41
|
+
{
|
42
|
+
name: dsl_model.inflected_class_name,
|
43
|
+
partitioners: dsl_model.inherited_partitioners,
|
44
|
+
table: dsl_model.inherited_table_name,
|
45
|
+
relationships: {}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def append_model_and_relationship(schema_spec, base_model, association_name)
|
50
|
+
association = find_association!(base_model, association_name)
|
51
|
+
target_model = association.model_constant
|
52
|
+
|
53
|
+
schema_spec[target_model.inflected_class_name] ||= model_config(target_model)
|
54
|
+
|
55
|
+
schema_spec[base_model.inflected_class_name][:relationships][association.name] = {
|
56
|
+
constraints: association.constraints,
|
57
|
+
model: relationship_model_name(association, target_model)
|
58
|
+
}
|
59
|
+
|
60
|
+
target_model
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a unique list of ancestor paths from a key_chain. Omits any
|
64
|
+
# fields on the base model.
|
65
|
+
def ancestor_paths(key_chain)
|
66
|
+
key_chain.to_unique_ancestors.key_path_set.select do |key_path|
|
67
|
+
key_path.ancestor_names.any?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_association!(base_model, association_name)
|
72
|
+
base_model.inherited_associations.find { |assoc| assoc.name == association_name } \
|
73
|
+
||
|
74
|
+
raise(
|
75
|
+
ArgumentError,
|
76
|
+
"no association #{association_name} exists on model #{base_model.name}"
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def relationship_model_name(association, target_model)
|
81
|
+
return nil if association.name == target_model.inflected_class_name
|
82
|
+
|
83
|
+
target_model.inflected_class_name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/dbee/key_chain.rb
CHANGED
@@ -38,5 +38,16 @@ module Dbee
|
|
38
38
|
|
39
39
|
ancestor_path_set.include?(path)
|
40
40
|
end
|
41
|
+
|
42
|
+
# Returns a unique set of ancestors by considering all column names to be the same.
|
43
|
+
def to_unique_ancestors # :nodoc:
|
44
|
+
normalized_paths = key_path_set.map do |kp|
|
45
|
+
KeyPath.new((kp.ancestor_names + COLUMN_PLACEHOLDER).join(KeyPath::SPLIT_CHAR))
|
46
|
+
end
|
47
|
+
self.class.new(normalized_paths.uniq)
|
48
|
+
end
|
49
|
+
|
50
|
+
COLUMN_PLACEHOLDER = ['any_column'].freeze
|
51
|
+
private_constant :COLUMN_PLACEHOLDER
|
41
52
|
end
|
42
53
|
end
|
data/lib/dbee/model.rb
CHANGED
@@ -9,65 +9,53 @@
|
|
9
9
|
|
10
10
|
require_relative 'model/constraints'
|
11
11
|
require_relative 'model/partitioner'
|
12
|
+
require_relative 'model/relationships'
|
13
|
+
require_relative 'util/make_keyed_by'
|
12
14
|
|
13
15
|
module Dbee
|
14
16
|
# In DB terms, a Model is usually a table, but it does not have to be. You can also re-model
|
15
17
|
# your DB schema using Dbee::Models.
|
16
18
|
class Model
|
17
|
-
extend Forwardable
|
18
19
|
acts_as_hashable
|
20
|
+
extend Dbee::Util::MakeKeyedBy
|
21
|
+
extend Forwardable
|
19
22
|
|
20
23
|
class ModelNotFoundError < StandardError; end
|
21
24
|
|
22
|
-
attr_reader :constraints, :filters, :name, :partitioners, :table
|
25
|
+
attr_reader :constraints, :filters, :name, :partitioners, :relationships, :table
|
23
26
|
|
24
27
|
def_delegator :models_by_name, :values, :models
|
25
28
|
def_delegator :models, :sort, :sorted_models
|
26
29
|
def_delegator :constraints, :sort, :sorted_constraints
|
27
30
|
def_delegator :partitioners, :sort, :sorted_partitioners
|
28
31
|
|
29
|
-
def initialize(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def initialize(
|
33
|
+
name:,
|
34
|
+
constraints: [], # Exists here for tree based model backward compatibility.
|
35
|
+
relationships: [],
|
36
|
+
models: [], # Exists here for tree based model backward compatibility.
|
37
|
+
partitioners: [],
|
38
|
+
table: ''
|
39
|
+
)
|
40
|
+
@name = name
|
41
|
+
@constraints = Constraints.array(constraints || []).uniq
|
42
|
+
@relationships = Relationships.make_keyed_by(:name, relationships)
|
34
43
|
@models_by_name = name_hash(Model.array(models))
|
35
44
|
@partitioners = Partitioner.array(partitioners).uniq
|
36
45
|
@table = table.to_s.empty? ? @name : table.to_s
|
37
46
|
|
47
|
+
ensure_input_is_valid
|
48
|
+
|
38
49
|
freeze
|
39
50
|
end
|
40
51
|
|
41
|
-
|
42
|
-
|
43
|
-
# The hash key will be an array of strings (model names) and the value will be the
|
44
|
-
# identified model.
|
45
|
-
def ancestors!(parts = [], visited_parts = [], found = {})
|
46
|
-
return found if Array(parts).empty?
|
47
|
-
|
48
|
-
# Take the first entry in parts
|
49
|
-
model_name = parts.first.to_s
|
50
|
-
|
51
|
-
# Ensure we have it registered as a child, or raise error
|
52
|
-
model = assert_model(model_name, visited_parts)
|
53
|
-
|
54
|
-
# Push onto visited list
|
55
|
-
visited_parts += [model_name]
|
56
|
-
|
57
|
-
# Add found model to flattened structure
|
58
|
-
found[visited_parts] = model
|
59
|
-
|
60
|
-
# Recursively call for next parts in the chain
|
61
|
-
model.ancestors!(parts[1..-1], visited_parts, found)
|
52
|
+
def relationship_for_name(relationship_name)
|
53
|
+
relationships[relationship_name]
|
62
54
|
end
|
63
55
|
|
64
56
|
def ==(other)
|
65
57
|
other.instance_of?(self.class) &&
|
66
|
-
other.name == name &&
|
67
|
-
other.table == table &&
|
68
|
-
other.sorted_constraints == sorted_constraints &&
|
69
|
-
other.sorted_partitioners == sorted_partitioners &&
|
70
|
-
other.sorted_models == sorted_models
|
58
|
+
other.name == name && other.table == table && children_are_equal(other)
|
71
59
|
end
|
72
60
|
alias eql? ==
|
73
61
|
|
@@ -75,17 +63,41 @@ module Dbee
|
|
75
63
|
name <=> other.name
|
76
64
|
end
|
77
65
|
|
66
|
+
def hash
|
67
|
+
[
|
68
|
+
name.hash,
|
69
|
+
table.hash,
|
70
|
+
relationships.hash,
|
71
|
+
sorted_constraints.hash,
|
72
|
+
sorted_partitioners.hash,
|
73
|
+
sorted_models.hash
|
74
|
+
].hash
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
name
|
79
|
+
end
|
80
|
+
|
78
81
|
private
|
79
82
|
|
80
83
|
attr_reader :models_by_name
|
81
84
|
|
82
|
-
def assert_model(model_name, visited_parts)
|
83
|
-
models_by_name[model_name] ||
|
84
|
-
raise(ModelNotFoundError, "Missing: #{model_name}, after: #{visited_parts}")
|
85
|
-
end
|
86
|
-
|
87
85
|
def name_hash(array)
|
88
86
|
array.map { |a| [a.name, a] }.to_h
|
89
87
|
end
|
88
|
+
|
89
|
+
def children_are_equal(other)
|
90
|
+
other.relationships == relationships &&
|
91
|
+
other.sorted_constraints == sorted_constraints &&
|
92
|
+
other.sorted_partitioners == sorted_partitioners &&
|
93
|
+
other.sorted_models == sorted_models
|
94
|
+
end
|
95
|
+
|
96
|
+
def ensure_input_is_valid
|
97
|
+
raise ArgumentError, 'name is required' if name.to_s.empty?
|
98
|
+
|
99
|
+
constraints&.any? && relationships&.any? && \
|
100
|
+
raise(ArgumentError, 'constraints and relationships are mutually exclusive')
|
101
|
+
end
|
90
102
|
end
|
91
103
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'relationships/basic'
|
11
|
+
require_relative '../util/make_keyed_by'
|
12
|
+
|
13
|
+
module Dbee
|
14
|
+
class Model
|
15
|
+
# Top-level class that allows for the making of relationships.
|
16
|
+
class Relationships
|
17
|
+
acts_as_hashable_factory
|
18
|
+
extend Dbee::Util::MakeKeyedBy
|
19
|
+
|
20
|
+
register 'basic', Basic
|
21
|
+
register '', Basic # When type is not present this will be the default
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Dbee
|
11
|
+
class Model
|
12
|
+
class Relationships
|
13
|
+
# A relationship from one model to another.
|
14
|
+
class Basic
|
15
|
+
acts_as_hashable
|
16
|
+
|
17
|
+
attr_reader :constraints, :model, :name
|
18
|
+
|
19
|
+
def initialize(name:, constraints: [], model: nil)
|
20
|
+
@name = name
|
21
|
+
raise ArgumentError, 'name is required' if name.to_s.empty?
|
22
|
+
|
23
|
+
@constraints = Constraints.array(constraints || []).uniq
|
24
|
+
@model = model
|
25
|
+
|
26
|
+
freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
def model_name
|
30
|
+
model || name
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash
|
34
|
+
[self.class.hash, name.hash, constraints.hash, model.hash].hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
other.instance_of?(self.class) &&
|
39
|
+
other.name == name &&
|
40
|
+
other.constraints == constraints &&
|
41
|
+
other.model == model
|
42
|
+
end
|
43
|
+
alias eql? ==
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/dbee/query.rb
CHANGED
@@ -23,6 +23,7 @@ module Dbee
|
|
23
23
|
|
24
24
|
attr_reader :fields,
|
25
25
|
:filters,
|
26
|
+
:from,
|
26
27
|
:limit,
|
27
28
|
:sorters
|
28
29
|
|
@@ -32,12 +33,14 @@ module Dbee
|
|
32
33
|
|
33
34
|
def initialize(
|
34
35
|
fields: [],
|
36
|
+
from: nil,
|
35
37
|
filters: [],
|
36
38
|
limit: nil,
|
37
39
|
sorters: []
|
38
40
|
)
|
39
41
|
@fields = Field.array(fields)
|
40
42
|
@filters = Filters.array(filters).uniq
|
43
|
+
@from = from.to_s
|
41
44
|
@limit = limit.to_s.empty? ? nil : limit.to_i
|
42
45
|
@sorters = Sorters.array(sorters).uniq
|
43
46
|
|
@@ -47,6 +50,7 @@ module Dbee
|
|
47
50
|
def ==(other)
|
48
51
|
other.instance_of?(self.class) &&
|
49
52
|
other.limit == limit &&
|
53
|
+
other.from == from &&
|
50
54
|
other.sorted_fields == sorted_fields &&
|
51
55
|
other.sorted_filters == sorted_filters &&
|
52
56
|
other.sorted_sorters == sorted_sorters
|
data/lib/dbee/query/field.rb
CHANGED
@@ -26,12 +26,7 @@ module Dbee
|
|
26
26
|
|
27
27
|
attr_reader :aggregator, :display, :filters, :key_path
|
28
28
|
|
29
|
-
def initialize(
|
30
|
-
aggregator: nil,
|
31
|
-
display: nil,
|
32
|
-
filters: [],
|
33
|
-
key_path:
|
34
|
-
)
|
29
|
+
def initialize(key_path:, aggregator: nil, display: nil, filters: [])
|
35
30
|
raise ArgumentError, 'key_path is required' if key_path.to_s.empty?
|
36
31
|
|
37
32
|
@aggregator = aggregator ? Aggregator.const_get(aggregator.to_s.upcase.to_sym) : nil
|
data/lib/dbee/schema.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Dbee
|
11
|
+
# A schema represents an entire graph of related models.
|
12
|
+
class Schema
|
13
|
+
attr_reader :models
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
def initialize(schema_config)
|
17
|
+
@models_by_name = Model.make_keyed_by(:name, schema_config)
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
# Given a Dbee::Model and Dbee::KeyPath, this returns a list of
|
23
|
+
# Dbee::Relationship and Dbee::Model tuples that lie on the key path.
|
24
|
+
# The returned list is a two dimensional array in
|
25
|
+
# the form of <tt>[[relationship, model], [relationship2, model2]]</tt>,
|
26
|
+
# etc. The relationships and models correspond to each ancestor part of the
|
27
|
+
# key path.
|
28
|
+
#
|
29
|
+
# The key_path argument can be either a Dbee::KeyPath or an array of
|
30
|
+
# string ancestor names.
|
31
|
+
#
|
32
|
+
# An exception is raised of the provided key_path contains relationship
|
33
|
+
# names that do not exist in this schema.
|
34
|
+
def expand_query_path(model, key_path, query_path = [])
|
35
|
+
ancestors = key_path.respond_to?(:ancestor_names) ? key_path.ancestor_names : key_path
|
36
|
+
relationship_name = ancestors.first
|
37
|
+
return query_path unless relationship_name
|
38
|
+
|
39
|
+
relationship = relationship_for_name!(model, relationship_name)
|
40
|
+
join_model = model_for_name!(relationship.model_name)
|
41
|
+
expand_query_path(
|
42
|
+
join_model,
|
43
|
+
ancestors.drop(1),
|
44
|
+
query_path + [[relationship_for_name!(model, relationship_name), join_model]]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def model_for_name!(model_name)
|
49
|
+
models_by_name[model_name.to_s] || raise(Model::ModelNotFoundError, model_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
other.instance_of?(self.class) && other.send(:models_by_name) == models_by_name
|
54
|
+
end
|
55
|
+
alias eql? ==
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :models_by_name
|
60
|
+
|
61
|
+
def relationship_for_name!(model, rel_name)
|
62
|
+
model.relationship_for_name(rel_name) ||
|
63
|
+
raise("model '#{model.name}' does not have a '#{rel_name}' relationship")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Dbee
|
11
|
+
# This creates a Dbee::Schema from a variety of different inputs:
|
12
|
+
#
|
13
|
+
# 1. The hash representation of a schema.
|
14
|
+
# 2. A Dbee::Base subclass (code based models).
|
15
|
+
#
|
16
|
+
# For backward compatibility, tree based models are also supported in the
|
17
|
+
# following formats:
|
18
|
+
#
|
19
|
+
# 3. The hash representation of a tree based model.
|
20
|
+
# 4. Dbee::Model instances in tree based form (using the deprecated constraints and models
|
21
|
+
# attributes).
|
22
|
+
class SchemaCreator # :nodoc:
|
23
|
+
attr_reader :schema
|
24
|
+
|
25
|
+
# An ArgumentError is raised if the query "from" attribute differs from the name of the root
|
26
|
+
# model of a tree based model or if the "from" attribute is blank.
|
27
|
+
def initialize(schema_or_model, query)
|
28
|
+
@orig_query = Query.make(query) || raise(ArgumentError, 'query is required')
|
29
|
+
raise ArgumentError, 'a schema or model is required' unless schema_or_model
|
30
|
+
|
31
|
+
@schema = make_schema(schema_or_model)
|
32
|
+
|
33
|
+
# Note that for backward compatibility reasons, this validation does not
|
34
|
+
# exist in the DBee::Query class. This allows continued support for
|
35
|
+
# old callers who depend on the "from" field being inferred from the root
|
36
|
+
# tree model name.
|
37
|
+
raise ArgumentError, 'query requires a from model name' if expected_from_model.empty?
|
38
|
+
|
39
|
+
validate_query_from_model!
|
40
|
+
|
41
|
+
freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a Dbee::Query instance with a "from" attribute which is
|
45
|
+
# sometimes derived for tree based models.
|
46
|
+
def query
|
47
|
+
return orig_query if expected_from_model == orig_query.from
|
48
|
+
|
49
|
+
Query.new(
|
50
|
+
from: expected_from_model,
|
51
|
+
fields: orig_query.fields,
|
52
|
+
filters: orig_query.filters,
|
53
|
+
sorters: orig_query.sorters,
|
54
|
+
limit: orig_query.limit
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :orig_query, :expected_from_model
|
61
|
+
|
62
|
+
def make_schema(input)
|
63
|
+
@expected_from_model = orig_query.from
|
64
|
+
|
65
|
+
return input if input.is_a?(Dbee::Schema)
|
66
|
+
|
67
|
+
if input.respond_to?(:to_schema)
|
68
|
+
@expected_from_model = input.inflected_class_name
|
69
|
+
return input.to_schema(orig_query.key_chain)
|
70
|
+
end
|
71
|
+
|
72
|
+
model_or_schema = to_object(input)
|
73
|
+
if model_or_schema.is_a?(Model)
|
74
|
+
@expected_from_model = model_or_schema.name.to_s
|
75
|
+
SchemaFromTreeBasedModel.convert(model_or_schema)
|
76
|
+
else
|
77
|
+
model_or_schema
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_object(input)
|
82
|
+
return input unless input.is_a?(Hash)
|
83
|
+
|
84
|
+
if tree_based_hash?(input)
|
85
|
+
Model.make(input)
|
86
|
+
else
|
87
|
+
Schema.new(input)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_query_from_model!
|
92
|
+
!orig_query.from.empty? && expected_from_model.to_s != orig_query.from.to_s && \
|
93
|
+
raise(
|
94
|
+
ArgumentError,
|
95
|
+
"expected from model to be '#{expected_from_model}' but got '#{orig_query.from}'"
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def tree_based_hash?(hash)
|
100
|
+
name = hash[:name] || hash['name']
|
101
|
+
|
102
|
+
# In the unlikely event that schema based hash had a model called "name",
|
103
|
+
# its value would either be nil or a hash.
|
104
|
+
name.is_a?(String) || name.is_a?(Symbol)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|