dbee 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc7bf9701c14a96bfe5c83893637a1e10545e70f268c674851279d150b57bcc4
4
- data.tar.gz: 1fbdb14e73fe98629a2cf41db0906b543eb33d16e044d5c8552916c76d4e9e8d
3
+ metadata.gz: 3e07996f0622fbf3eb19b8fa7bdd6ebfe963d3edc18745ccdf89f2a87f7a16dd
4
+ data.tar.gz: 0e25d666b9cd0a099741220caf423a670e832149bac26f27283c7a43987c14c5
5
5
  SHA512:
6
- metadata.gz: 6ecaedc494e15776f1716de8891dd528fcbbbb76e44d6fe4d3056ab70674ede17f029f5316c171680c9265074624de8d78cf8856957fa5242371c26e41297089
7
- data.tar.gz: e091625f6c6281673c48df689a1ec3ec692898799f9ef76d28991c90c519526bcd3c09d19d0cd1ce72a1df35bc98b5f81b46b01ec25934b4aa7ea86635549d1e
6
+ metadata.gz: 5290d4c90695c68b7d558c35cf4ca47cd24d60e50fc776ce2c4138a74d469e86bc9021d66a9645ddcb0e25924cad80d1dca239df610a1c01a41c02523159062e
7
+ data.tar.gz: b9023ad0e9a82db914a0c0c40b918fd52a87572718aadc31e82eb1ba96adbd7a8a50932d9d1968b72d73de9689f26d4ca71374e4a7c2fcba416fd5f4e992c696
data/CHANGELOG.md CHANGED
@@ -1,8 +1,19 @@
1
+ # 1.0.2 (August 26th, 2019)
2
+
3
+ Fixes:
4
+
5
+ * Dbee::Base subclasses can now support cycles to N-depth, where N is limited by the Query. The recursion will go as far as the Query specifies it has to go.
6
+
7
+ Additions:
8
+
9
+ * Equals filter is now the default type when type is omitted.
10
+ * model can now be a string so it can be lazy evaluated at run-time instead of at script evaluation time.
11
+
1
12
  # 1.0.1 (August 26th, 2019)
2
13
 
3
14
  Fixes:
4
15
 
5
- * Dbee::Base sub-classes can now declare self-referential associations. Note that it will stop the hierarchy once the cycle is detected.
16
+ * Dbee::Base subclasses can now declare self-referential associations. Note that it will stop the hierarchy once the cycle is detected.
6
17
 
7
18
  Additions:
8
19
 
data/lib/dbee/base.rb CHANGED
@@ -24,11 +24,23 @@ module Dbee
24
24
  self
25
25
  end
26
26
 
27
- def to_model(name = nil, constraints = [], from = nil)
28
- name = derive_name(name)
29
- key = [name, constraints, from]
30
-
31
- to_models[key] ||= Model.make(model_config(name, constraints, from))
27
+ # This method is cycle-resistant due to the fact that it is a requirement to send in a
28
+ # key_chain. That means each model produced using to_model is specific to a set of desired
29
+ # fields. Basically, you cannot derive a Model from a Base subclass without the context
30
+ # of a Query. This is not true for configuration-first Model definitions because, in that
31
+ # case, cycles do not exist since the nature of the configuration is flat.
32
+ def to_model(key_chain, name = nil, constraints = [], path_parts = [])
33
+ derived_name = derive_name(name)
34
+ key = [key_chain, derived_name, constraints, path_parts]
35
+
36
+ to_models[key] ||= Model.make(
37
+ model_config(
38
+ key_chain,
39
+ derived_name,
40
+ constraints,
41
+ path_parts + [name]
42
+ )
43
+ )
32
44
  end
33
45
 
34
46
  def table_name
@@ -63,10 +75,10 @@ module Dbee
63
75
  subclasses.reverse
64
76
  end
65
77
 
66
- def model_config(name, constraints, from)
78
+ def model_config(key_chain, name, constraints, path_parts)
67
79
  {
68
80
  constraints: constraints,
69
- models: associations(from),
81
+ models: associations(key_chain, path_parts),
70
82
  name: name,
71
83
  table: derive_table
72
84
  }
@@ -82,15 +94,15 @@ module Dbee
82
94
  inherited_table.empty? ? inflected_name : inherited_table
83
95
  end
84
96
 
85
- def associations(from)
97
+ def associations(key_chain, path_parts)
86
98
  inherited_associations_by_name.values
87
- .reject { |c| c[:name].to_s == from.to_s }
99
+ .select { |c| key_chain.ancestor_path?(path_parts, c[:name]) }
88
100
  .each_with_object([]) do |config, memo|
89
- model_klass = config[:model]
101
+ model_constant = constantize(config[:model])
90
102
  associated_constraints = config[:constraints]
91
103
  name = config[:name]
92
104
 
93
- memo << model_klass.to_model(name, associated_constraints, name)
105
+ memo << model_constant.to_model(key_chain, name, associated_constraints, path_parts)
94
106
  end
95
107
  end
96
108
 
@@ -106,6 +118,10 @@ module Dbee
106
118
  .tr('-', '_')
107
119
  .downcase
108
120
  end
121
+
122
+ def constantize(value)
123
+ value.is_a?(String) || value.is_a?(Symbol) ? Object.const_get(value) : value
124
+ end
109
125
  end
110
126
  end
111
127
  end
@@ -0,0 +1,42 @@
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 'key_path'
11
+
12
+ module Dbee
13
+ # A KeyChain is a collection of KeyPath objects. It knows how to deal with aggregate methods,
14
+ # such as equality of a set of KeyPath objects and finding an ancestor path in all the
15
+ # KeyPath objects' ancestor paths. You can pass in either KeyPath instances or strings,
16
+ # which will be coerced to KeyPath objects. Duplicates will also be removed.
17
+ class KeyChain
18
+ attr_reader :key_path_set, :ancestor_path_set
19
+
20
+ def initialize(key_paths = [])
21
+ @key_path_set = key_paths.map { |k| KeyPath.get(k) }.to_set
22
+ @ancestor_path_set = @key_path_set.map(&:ancestor_paths).flatten.to_set
23
+
24
+ freeze
25
+ end
26
+
27
+ def hash
28
+ key_path_set.hash
29
+ end
30
+
31
+ def ==(other)
32
+ key_path_set == other.key_path_set
33
+ end
34
+ alias eql? ==
35
+
36
+ def ancestor_path?(*parts)
37
+ path = parts.flatten.compact.join(KeyPath::SPLIT_CHAR)
38
+
39
+ ancestor_path_set.include?(path)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
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 class represents a relative path from a model to a column. For example:
12
+ # Say we have a model called "users" which is represented by a "users" table.
13
+ # The "users" table also has a one-to-many relationship with a "phone_numbers" table, which
14
+ # is modeled as a nested model under "users" as "phone_numbers". Then, to get the column:
15
+ # "area_code", you would use: "phone_numbers.area_code".
16
+ # Say the column "name" is located on "users", you could use the key path: "name".
17
+ # This also works for deeper nested columns in the same fashion.
18
+ class KeyPath
19
+ extend Forwardable
20
+
21
+ class << self
22
+ def get(obj)
23
+ obj.is_a?(self.class) ? obj : new(obj)
24
+ end
25
+ end
26
+
27
+ SPLIT_CHAR = '.'
28
+
29
+ attr_reader :value, :ancestor_names, :column_name
30
+
31
+ def_delegators :value, :to_s
32
+
33
+ def initialize(value)
34
+ raise 'Value is required' if value.to_s.empty?
35
+
36
+ @value = value.to_s
37
+ @ancestor_names = value.to_s.split(SPLIT_CHAR)
38
+ @column_name = @ancestor_names.pop
39
+
40
+ freeze
41
+ end
42
+
43
+ def hash
44
+ value.hash
45
+ end
46
+
47
+ def ==(other)
48
+ other.to_s == to_s
49
+ end
50
+ alias eql? ==
51
+
52
+ def ancestor_paths
53
+ ancestor_names.each_with_object([]) do |ancestor, memo|
54
+ memo << [memo.last, ancestor].compact.join(SPLIT_CHAR)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -23,7 +23,7 @@ module Dbee
23
23
  end
24
24
 
25
25
  def <=>(other)
26
- other.name <=> name
26
+ name <=> other.name
27
27
  end
28
28
 
29
29
  def hash
data/lib/dbee/model.rb CHANGED
@@ -21,7 +21,7 @@ module Dbee
21
21
 
22
22
  class ModelNotFoundError < StandardError; end
23
23
 
24
- attr_reader :constraints, :name
24
+ attr_reader :constraints, :name, :table
25
25
 
26
26
  def initialize(name:, constraints: [], models: [], table: '')
27
27
  raise ArgumentError, 'name is required' if name.to_s.empty?
@@ -29,7 +29,7 @@ module Dbee
29
29
  @name = name.to_s
30
30
  @constraints = Constraints.array(constraints)
31
31
  @models_by_name = name_hash(Model.array(models))
32
- @table = table.to_s
32
+ @table = table.to_s.empty? ? @name : table.to_s
33
33
 
34
34
  freeze
35
35
  end
@@ -38,10 +38,6 @@ module Dbee
38
38
  array.map { |a| [a.name, a] }.to_h
39
39
  end
40
40
 
41
- def table
42
- @table.to_s.empty? ? name : @table
43
- end
44
-
45
41
  def models
46
42
  models_by_name.values
47
43
  end
@@ -75,7 +71,7 @@ module Dbee
75
71
  alias eql? ==
76
72
 
77
73
  def <=>(other)
78
- other.name <=> name
74
+ name <=> other.name
79
75
  end
80
76
 
81
77
  private
@@ -7,8 +7,6 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'key_path'
11
-
12
10
  module Dbee
13
11
  class Query
14
12
  # This class is an abstraction of the SELECT part of a SQL statement.
@@ -36,6 +34,10 @@ module Dbee
36
34
  other.key_path == key_path && other.display == display
37
35
  end
38
36
  alias eql? ==
37
+
38
+ def <=>(other)
39
+ "#{key_path}#{display}" <=> "#{other.key_path}#{other.display}"
40
+ end
39
41
  end
40
42
  end
41
43
  end
@@ -33,6 +33,10 @@ module Dbee
33
33
  other.key_path == key_path && other.value == value
34
34
  end
35
35
  alias eql? ==
36
+
37
+ def <=>(other)
38
+ "#{key_path}#{value}" <=> "#{other.key_path}#{other.value}"
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -25,6 +25,7 @@ module Dbee
25
25
  class Filters
26
26
  acts_as_hashable_factory
27
27
 
28
+ register '', Equals # Default if type is blank.
28
29
  register 'contains', Contains
29
30
  register 'equals', Equals
30
31
  register 'greater_than_or_equal_to', GreaterThanOrEqualTo
@@ -7,8 +7,6 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'key_path'
11
-
12
10
  module Dbee
13
11
  class Query
14
12
  # Abstract representation of the ORDER BY part of a SQL statement.
@@ -48,6 +46,10 @@ module Dbee
48
46
  other.key_path == key_path && other.direction == direction
49
47
  end
50
48
  alias eql? ==
49
+
50
+ def <=>(other)
51
+ "#{key_path}#{direction}" <=> "#{other.key_path}#{other.direction}"
52
+ end
51
53
  end
52
54
  end
53
55
  end
data/lib/dbee/query.rb CHANGED
@@ -40,11 +40,21 @@ module Dbee
40
40
  end
41
41
 
42
42
  def ==(other)
43
- other.fields == fields &&
44
- other.filters == filters &&
43
+ other.fields.sort == fields.sort &&
44
+ other.filters.sort == filters.sort &&
45
45
  other.limit == limit &&
46
- other.sorters == sorters
46
+ other.sorters.sort == sorters.sort
47
47
  end
48
48
  alias eql? ==
49
+
50
+ def key_chain
51
+ KeyChain.new(key_paths)
52
+ end
53
+
54
+ private
55
+
56
+ def key_paths
57
+ (fields.map(&:key_path) + filters.map(&:key_path) + sorters.map(&:key_path))
58
+ end
49
59
  end
50
60
  end
data/lib/dbee/version.rb CHANGED
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module Dbee
11
- VERSION = '1.0.1'
11
+ VERSION = '1.0.2'
12
12
  end
data/lib/dbee.rb CHANGED
@@ -11,6 +11,8 @@ require 'acts_as_hashable'
11
11
  require 'forwardable'
12
12
 
13
13
  require_relative 'dbee/base'
14
+ require_relative 'dbee/key_chain'
15
+ require_relative 'dbee/key_path'
14
16
  require_relative 'dbee/model'
15
17
  require_relative 'dbee/query'
16
18
  require_relative 'dbee/providers'
@@ -19,8 +21,13 @@ require_relative 'dbee/providers'
19
21
  module Dbee
20
22
  class << self
21
23
  def sql(model, query, provider)
22
- model = model.is_a?(Hash) || model.is_a?(Model) ? Model.make(model) : model.to_model
23
24
  query = Query.make(query)
25
+ model =
26
+ if model.is_a?(Hash) || model.is_a?(Model)
27
+ Model.make(model)
28
+ else
29
+ model.to_model(query.key_chain)
30
+ end
24
31
 
25
32
  provider.sql(model, query)
26
33
  end
@@ -11,13 +11,53 @@ require 'spec_helper'
11
11
  require 'fixtures/models'
12
12
 
13
13
  describe Dbee::Base do
14
- it 'compiles to Model instance correctly' do
15
- model_name = 'Theaters, Members, and Movies'
16
- expected_config = yaml_fixture('models.yaml')[model_name]
14
+ describe '#to_model' do
15
+ it 'compiles correctly' do
16
+ model_name = 'Theaters, Members, and Movies'
17
+ expected_config = yaml_fixture('models.yaml')[model_name]
17
18
 
18
- expected_model = Dbee::Model.make(expected_config)
19
+ expected_model = Dbee::Model.make(expected_config)
19
20
 
20
- expect(Models::Theaters.to_model).to eq(expected_model)
21
+ key_paths = %w[
22
+ members.demos.phone_numbers.a
23
+ members.movies.b
24
+ members.favorite_comic_movies.c
25
+ members.favorite_mystery_movies.d
26
+ members.favorite_comedy_movies.e
27
+ parent_theater.members.demos.phone_numbers.f
28
+ parent_theater.members.movies.g
29
+ parent_theater.members.favorite_comic_movies.h
30
+ parent_theater.members.favorite_mystery_movies.i
31
+ parent_theater.members.favorite_comedy_movies.j
32
+ ]
33
+
34
+ key_chain = Dbee::KeyChain.new(key_paths)
35
+
36
+ actual_model = Models::Theaters.to_model(key_chain)
37
+
38
+ expect(actual_model).to eq(expected_model)
39
+ end
40
+
41
+ it 'honors key_chain to flatten cyclic references' do
42
+ model_name = 'Cycle Example'
43
+ expected_config = yaml_fixture('models.yaml')[model_name]
44
+
45
+ expected_model = Dbee::Model.make(expected_config)
46
+
47
+ key_paths = %w[
48
+ b1.c.a.z
49
+ b1.d.a.y
50
+ b2.c.a.x
51
+ b2.d.a.w
52
+ b2.d.a.b1.c.z
53
+ ]
54
+
55
+ key_chain = Dbee::KeyChain.new(key_paths)
56
+
57
+ actual_model = Cycles::A.to_model(key_chain)
58
+
59
+ expect(actual_model).to eq(expected_model)
60
+ end
21
61
  end
22
62
 
23
63
  context 'inheritance' do
@@ -0,0 +1,81 @@
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 'spec_helper'
11
+
12
+ describe Dbee::KeyChain do
13
+ let(:key_paths) do
14
+ [
15
+ 'z.b.c.d.e',
16
+ 'b.c.s',
17
+ 'a',
18
+ 'b.c.s',
19
+ 123,
20
+ Dbee::KeyPath.get('44.55.66'),
21
+ 'a'
22
+ ]
23
+ end
24
+
25
+ let(:out_of_order_key_paths) do
26
+ [
27
+ Dbee::KeyPath.get('44.55.66'),
28
+ Dbee::KeyPath.get('44.55.66'),
29
+ Dbee::KeyPath.get('44.55.66'),
30
+ Dbee::KeyPath.get('b.c.s'),
31
+ Dbee::KeyPath.get(123),
32
+ Dbee::KeyPath.get('z.b.c.d.e'),
33
+ Dbee::KeyPath.get('44.55.66'),
34
+ Dbee::KeyPath.get('a')
35
+ ]
36
+ end
37
+
38
+ subject { described_class.new(key_paths) }
39
+
40
+ describe '#initialize' do
41
+ it 'sets key_path_set correctly' do
42
+ expect(subject.key_path_set).to eq(out_of_order_key_paths.to_set)
43
+ end
44
+
45
+ it 'sets ancestor_path_set correctly' do
46
+ expected = out_of_order_key_paths.map(&:ancestor_paths).flatten.to_set
47
+
48
+ expect(subject.ancestor_path_set).to eq(expected)
49
+ end
50
+ end
51
+
52
+ specify 'equality compares unique key_paths' do
53
+ chain1 = described_class.new(key_paths)
54
+ chain2 = described_class.new(out_of_order_key_paths)
55
+
56
+ expect(chain1).to eq(chain2)
57
+ expect(chain1).to eql(chain2)
58
+ end
59
+
60
+ specify '#hash compares unique key_path hashes' do
61
+ chain1 = described_class.new(key_paths)
62
+ chain2 = described_class.new(out_of_order_key_paths)
63
+
64
+ expect(chain1.hash).to eq(chain2.hash)
65
+ expect(chain1.hash).to eql(chain2.hash)
66
+ end
67
+
68
+ describe '#ancestor_path?' do
69
+ it 'returns true when passed in ancestor_path exists' do
70
+ %w[z z.b z.b.c z.b.c.d b b.c 44 44.55].each do |example|
71
+ expect(subject.ancestor_path?(example)).to be true
72
+ end
73
+ end
74
+
75
+ it 'returns false when passed in ancestor_path does not exist' do
76
+ %w[z.b.c.d.e matt nick sam.nick A Z].each do |example|
77
+ expect(subject.ancestor_path?(example)).to be false
78
+ end
79
+ end
80
+ end
81
+ end
@@ -9,7 +9,7 @@
9
9
 
10
10
  require 'spec_helper'
11
11
 
12
- describe Dbee::Query::KeyPath do
12
+ describe Dbee::KeyPath do
13
13
  let(:key_path_string) { 'contacts.demographics.first' }
14
14
 
15
15
  subject { described_class.get(key_path_string) }
@@ -32,4 +32,16 @@ describe Dbee::Query::KeyPath do
32
32
  expect(subject).to eq(key_path_string)
33
33
  expect(subject).to eql(key_path_string)
34
34
  end
35
+
36
+ describe '#ancestor_paths' do
37
+ let(:key_path_string) { 'a.b.c.d.e' }
38
+
39
+ subject { described_class.get(key_path_string) }
40
+
41
+ it 'should include all left-inclusive paths' do
42
+ expected = %w[a a.b a.b.c a.b.c.d]
43
+
44
+ expect(subject.ancestor_paths).to eq(expected)
45
+ end
46
+ end
35
47
  end
@@ -103,11 +103,20 @@ describe Dbee::Model do
103
103
 
104
104
  context 'README examples' do
105
105
  specify 'code-first and configuration-first models are equal' do
106
- config = yaml_fixture('models.yaml')['Readme']
106
+ config = yaml_fixture('models.yaml')['Readme']
107
+ config_model = described_class.make(config)
107
108
 
108
- config_model = described_class.make(config)
109
+ key_chain = Dbee::KeyChain.new(%w[
110
+ patients.a
111
+ patients.notes.b
112
+ patients.work_phone_number.c
113
+ patients.cell_phone_number.d
114
+ patients.fax_phone_number.e
115
+ ])
109
116
 
110
- expect(config_model).to eq(ReadmeDataModels::Practices.to_model)
117
+ code_model = ReadmeDataModels::Practices.to_model(key_chain)
118
+
119
+ expect(config_model).to eq(code_model)
111
120
  end
112
121
  end
113
122
  end
@@ -21,6 +21,10 @@ describe Dbee::Query do
21
21
  { key_path: :sort_me },
22
22
  { key_path: :sort_me_too, direction: :descending }
23
23
  ],
24
+ filters: [
25
+ { key_path: 'filter_me.a_little', value: 'something' },
26
+ { key_path: 'matt.nick.sam', value: 'something' }
27
+ ],
24
28
  limit: 125
25
29
  }
26
30
  end
@@ -41,6 +45,19 @@ describe Dbee::Query do
41
45
  end
42
46
  end
43
47
 
48
+ describe '#key_chain' do
49
+ it 'should include filter, sorter, and field key_paths' do
50
+ key_paths =
51
+ config[:fields].map { |f| f[:key_path].to_s } +
52
+ config[:filters].map { |f| f[:key_path].to_s } +
53
+ config[:sorters].map { |s| s[:key_path].to_s }
54
+
55
+ expected_key_chain = Dbee::KeyChain.new(key_paths)
56
+
57
+ expect(subject.key_chain).to eq(expected_key_chain)
58
+ end
59
+ end
60
+
44
61
  context 'README examples' do
45
62
  EXAMPLES = {
46
63
  'Get all practices' => {
data/spec/dbee_spec.rb CHANGED
@@ -14,47 +14,58 @@ describe Dbee do
14
14
  describe '#sql' do
15
15
  let(:provider) { Dbee::Providers::NullProvider.new }
16
16
 
17
- let(:query) { { fields: [{ key_path: :a }] } }
17
+ let(:model_hash) do
18
+ {
19
+ name: 'something'
20
+ }
21
+ end
18
22
 
19
- it 'accepts a hash as a model and passes a Model instance to provider#sql' do
20
- model = { name: 'something' }
23
+ let(:model) { Dbee::Model.make(model_hash) }
21
24
 
22
- expect(provider).to receive(:sql).with(Dbee::Model.make(model), Dbee::Query.make(query))
25
+ let(:query_hash) do
26
+ {
27
+ fields: [
28
+ { key_path: :a }
29
+ ]
30
+ }
31
+ end
23
32
 
24
- described_class.sql(model, query, provider)
33
+ let(:query) { Dbee::Query.make(query_hash) }
34
+
35
+ it 'accepts a hash as a model and passes a Model instance to provider#sql' do
36
+ expect(provider).to receive(:sql).with(model, query)
37
+
38
+ described_class.sql(model_hash, query, provider)
25
39
  end
26
40
 
27
41
  it 'accepts a Dbee::Model instance as a model and passes a Model instance to provider#sql' do
28
- model = Dbee::Model.make(name: 'something')
29
-
30
- expect(provider).to receive(:sql).with(Dbee::Model.make(model), Dbee::Query.make(query))
42
+ expect(provider).to receive(:sql).with(model, query)
31
43
 
32
44
  described_class.sql(model, query, provider)
33
45
  end
34
46
 
35
47
  it 'accepts a Dbee::Base constant as a model and passes a Model instance to provider#sql' do
36
- model = Models::Theaters
48
+ model_constant = Models::Theaters
37
49
 
38
- expect(provider).to receive(:sql).with(model.to_model, Dbee::Query.make(query))
50
+ expect(provider).to receive(:sql).with(model_constant.to_model(query.key_chain), query)
39
51
 
40
- described_class.sql(model, query, provider)
52
+ described_class.sql(model_constant, query, provider)
41
53
  end
42
54
 
43
55
  it 'accepts a Dbee::Query instance as a query and passes a Query instance to provider#sql' do
44
- model = Models::Theaters
45
- query = Dbee::Query.make(query)
56
+ model = Models::Theaters.to_model(query.key_chain)
46
57
 
47
- expect(provider).to receive(:sql).with(model.to_model, Dbee::Query.make(query))
58
+ expect(provider).to receive(:sql).with(model, query)
48
59
 
49
60
  described_class.sql(model, query, provider)
50
61
  end
51
62
 
52
63
  it 'accepts a hash as a query and passes a Query instance to provider#sql' do
53
- model = Models::Theaters
64
+ model = Models::Theaters.to_model(query.key_chain)
54
65
 
55
- expect(provider).to receive(:sql).with(model.to_model, Dbee::Query.make(query))
66
+ expect(provider).to receive(:sql).with(model, query)
56
67
 
57
- described_class.sql(model, query, provider)
68
+ described_class.sql(model, query_hash, provider)
58
69
  end
59
70
  end
60
71
  end
@@ -15,7 +15,7 @@ module Models
15
15
  end
16
16
 
17
17
  class Demographics < Dbee::Base
18
- association :phone_numbers, model: PhoneNumbers,
18
+ association :phone_numbers, model: 'Models::PhoneNumbers',
19
19
  constraints: {
20
20
  name: :demographic_id,
21
21
  parent: :id
@@ -56,7 +56,7 @@ module Models
56
56
 
57
57
  class Theaters < TheatersBase
58
58
  association :parent_theater, model: self, constraints: [
59
- { type: :reference, name: :parent_theater_id, parent: :id }
59
+ { type: :reference, name: :id, parent: :parent_theater_id }
60
60
  ]
61
61
  end
62
62
 
@@ -115,3 +115,27 @@ module ReadmeDataModels
115
115
  }
116
116
  end
117
117
  end
118
+
119
+ module Cycles
120
+ class BaseA < Dbee::Base
121
+ association :b1, model: 'Cycles::B'
122
+ end
123
+
124
+ class A < BaseA
125
+ association :b2, model: 'Cycles::B'
126
+ end
127
+
128
+ class B < Dbee::Base
129
+ association :c, model: 'Cycles::C'
130
+
131
+ association :d, model: 'Cycles::D'
132
+ end
133
+
134
+ class C < Dbee::Base
135
+ association :a, model: 'Cycles::A'
136
+ end
137
+
138
+ class D < Dbee::Base
139
+ association :a, model: 'Cycles::A'
140
+ end
141
+ end
@@ -60,8 +60,8 @@ Theaters, Members, and Movies:
60
60
  table: theaters
61
61
  constraints:
62
62
  - type: reference
63
- name: parent_theater_id
64
- parent: id
63
+ name: id
64
+ parent: parent_theater_id
65
65
  models:
66
66
  - name: members
67
67
  table: members
@@ -160,3 +160,29 @@ Readme:
160
160
  - type: static
161
161
  name: phone_number_type
162
162
  value: fax
163
+ Cycle Example:
164
+ name: a
165
+ models:
166
+ - name: b1
167
+ table: b
168
+ models:
169
+ - name: c
170
+ models:
171
+ - name: a
172
+ - name: d
173
+ models:
174
+ - name: a
175
+ - name: b2
176
+ table: b
177
+ models:
178
+ - name: c
179
+ models:
180
+ - name: a
181
+ - name: d
182
+ models:
183
+ - name: a
184
+ models:
185
+ - name: b1
186
+ table: b
187
+ models:
188
+ - name: c
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbee
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Ruggio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-26 00:00:00.000000000 Z
11
+ date: 2019-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_hashable
@@ -155,6 +155,8 @@ files:
155
155
  - dbee.gemspec
156
156
  - lib/dbee.rb
157
157
  - lib/dbee/base.rb
158
+ - lib/dbee/key_chain.rb
159
+ - lib/dbee/key_path.rb
158
160
  - lib/dbee/model.rb
159
161
  - lib/dbee/model/constraints.rb
160
162
  - lib/dbee/model/constraints/base.rb
@@ -176,10 +178,11 @@ files:
176
178
  - lib/dbee/query/filters/not_equals.rb
177
179
  - lib/dbee/query/filters/not_start_with.rb
178
180
  - lib/dbee/query/filters/starts_with.rb
179
- - lib/dbee/query/key_path.rb
180
181
  - lib/dbee/query/sorter.rb
181
182
  - lib/dbee/version.rb
182
183
  - spec/dbee/base_spec.rb
184
+ - spec/dbee/key_chain_spec.rb
185
+ - spec/dbee/key_path_spec.rb
183
186
  - spec/dbee/model/constraints/base_spec.rb
184
187
  - spec/dbee/model/constraints/reference_spec.rb
185
188
  - spec/dbee/model/constraints/static_spec.rb
@@ -189,7 +192,6 @@ files:
189
192
  - spec/dbee/query/field_spec.rb
190
193
  - spec/dbee/query/filters/base_spec.rb
191
194
  - spec/dbee/query/filters_spec.rb
192
- - spec/dbee/query/key_path_spec.rb
193
195
  - spec/dbee/query/sorter_spec.rb
194
196
  - spec/dbee/query_spec.rb
195
197
  - spec/dbee_spec.rb
@@ -221,6 +223,8 @@ specification_version: 4
221
223
  summary: Adhoc Reporting SQL Generator
222
224
  test_files:
223
225
  - spec/dbee/base_spec.rb
226
+ - spec/dbee/key_chain_spec.rb
227
+ - spec/dbee/key_path_spec.rb
224
228
  - spec/dbee/model/constraints/base_spec.rb
225
229
  - spec/dbee/model/constraints/reference_spec.rb
226
230
  - spec/dbee/model/constraints/static_spec.rb
@@ -230,7 +234,6 @@ test_files:
230
234
  - spec/dbee/query/field_spec.rb
231
235
  - spec/dbee/query/filters/base_spec.rb
232
236
  - spec/dbee/query/filters_spec.rb
233
- - spec/dbee/query/key_path_spec.rb
234
237
  - spec/dbee/query/sorter_spec.rb
235
238
  - spec/dbee/query_spec.rb
236
239
  - spec/dbee_spec.rb
@@ -1,56 +0,0 @@
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 Query
12
- # This class represents a relative path from a model to a column. For example:
13
- # Say we have a model called "users" which is represented by a "users" table.
14
- # The "users" table also has a one-to-many relationship with a "phone_numbers" table, which
15
- # is modeled as a nested model under "users" as "phone_numbers". Then, to get the column:
16
- # "area_code", you would use: "phone_numbers.area_code".
17
- # Say the column "name" is located on "users", you could use the key path: "name".
18
- # This also works for deeper nested columns in the same fashion.
19
- class KeyPath
20
- extend Forwardable
21
-
22
- class << self
23
- def get(obj)
24
- obj.is_a?(self.class) ? obj : new(obj)
25
- end
26
- end
27
-
28
- SPLIT_CHAR = '.'
29
-
30
- private_constant :SPLIT_CHAR
31
-
32
- attr_reader :value, :ancestor_names, :column_name
33
-
34
- def_delegators :value, :to_s
35
-
36
- def initialize(value)
37
- raise 'Value is required' if value.to_s.empty?
38
-
39
- @value = value.to_s
40
- @ancestor_names = value.to_s.split(SPLIT_CHAR)
41
- @column_name = @ancestor_names.pop
42
-
43
- freeze
44
- end
45
-
46
- def hash
47
- value.hash
48
- end
49
-
50
- def ==(other)
51
- other.to_s == to_s
52
- end
53
- alias eql? ==
54
- end
55
- end
56
- end