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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +42 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +43 -0
  7. data/Gemfile +7 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +22 -0
  10. data/README.md +102 -0
  11. data/Rakefile +7 -0
  12. data/SPEC.md +101 -0
  13. data/bin/guard +16 -0
  14. data/bin/rake +16 -0
  15. data/bin/rspec +16 -0
  16. data/bin/yard +16 -0
  17. data/clearly-query.gemspec +33 -0
  18. data/lib/clearly/query.rb +22 -0
  19. data/lib/clearly/query/cleaner.rb +63 -0
  20. data/lib/clearly/query/compose/comparison.rb +102 -0
  21. data/lib/clearly/query/compose/conditions.rb +215 -0
  22. data/lib/clearly/query/compose/core.rb +75 -0
  23. data/lib/clearly/query/compose/custom.rb +268 -0
  24. data/lib/clearly/query/compose/range.rb +114 -0
  25. data/lib/clearly/query/compose/special.rb +24 -0
  26. data/lib/clearly/query/compose/subset.rb +115 -0
  27. data/lib/clearly/query/composer.rb +269 -0
  28. data/lib/clearly/query/definition.rb +165 -0
  29. data/lib/clearly/query/errors.rb +27 -0
  30. data/lib/clearly/query/graph.rb +63 -0
  31. data/lib/clearly/query/helper.rb +50 -0
  32. data/lib/clearly/query/validate.rb +296 -0
  33. data/lib/clearly/query/version.rb +8 -0
  34. data/spec/lib/clearly/query/cleaner_spec.rb +42 -0
  35. data/spec/lib/clearly/query/compose/custom_spec.rb +77 -0
  36. data/spec/lib/clearly/query/composer_query_spec.rb +50 -0
  37. data/spec/lib/clearly/query/composer_spec.rb +422 -0
  38. data/spec/lib/clearly/query/definition_spec.rb +23 -0
  39. data/spec/lib/clearly/query/graph_spec.rb +81 -0
  40. data/spec/lib/clearly/query/helper_spec.rb +17 -0
  41. data/spec/lib/clearly/query/version_spec.rb +7 -0
  42. data/spec/spec_helper.rb +89 -0
  43. data/spec/support/db/migrate/001_db_create.rb +62 -0
  44. data/spec/support/models/customer.rb +63 -0
  45. data/spec/support/models/order.rb +66 -0
  46. data/spec/support/models/part.rb +63 -0
  47. data/spec/support/models/product.rb +67 -0
  48. data/spec/support/shared_setup.rb +13 -0
  49. data/tmp/.gitkeep +0 -0
  50. metadata +263 -0
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Definition do
4
+ include_context 'shared_setup'
5
+
6
+ it 'is not valid with nil model' do
7
+ expect {
8
+ Clearly::Query::Definition.new({hash: Customer.clearly_query_def})
9
+ }.to raise_error(Clearly::Query::QueryArgumentError, 'could not build definition from options')
10
+ end
11
+
12
+ it 'is not valid with nil hash' do
13
+ expect {
14
+ Clearly::Query::Definition.new({model: Customer})
15
+ }.to raise_error(Clearly::Query::QueryArgumentError, "value must not be empty, got ''")
16
+ end
17
+
18
+ it 'can be instantiated' do
19
+ Clearly::Query::Definition.new({model:Customer, hash: Customer.clearly_query_def})
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Graph do
4
+ include_context 'shared_setup'
5
+
6
+ context 'creates the expected branch when' do
7
+ it 'maps customer associations' do
8
+ association_graph = {join: customer_def.model, on: nil, associations: customer_def.associations}
9
+ graph = Clearly::Query::Graph.new(association_graph, :associations)
10
+ result = graph.branches
11
+
12
+ expect(result.size).to eq(1)
13
+ expect(result[0].size).to eq(6)
14
+
15
+ expect(result[0][0][:join]).to eq(Customer)
16
+ expect(result[0][1][:join]).to eq(Order)
17
+ expect(result[0][2][:join]).to eq(Arel::Table.new(:orders_products))
18
+ expect(result[0][3][:join]).to eq(Product)
19
+ expect(result[0][4][:join]).to eq(Arel::Table.new(:parts_products))
20
+ expect(result[0][5][:join]).to eq(Part)
21
+ end
22
+
23
+ it 'maps order associations' do
24
+ association_graph = {join: order_def.model, on: nil, associations: order_def.associations}
25
+ graph = Clearly::Query::Graph.new(association_graph, :associations)
26
+ result = graph.branches
27
+
28
+ expect(result.size).to eq(2)
29
+
30
+ expect(result[0].size).to eq(2)
31
+ expect(result[1].size).to eq(5)
32
+
33
+ expect(result[0][0][:join]).to eq(Order)
34
+ expect(result[0][1][:join]).to eq(Customer)
35
+
36
+ expect(result[1][0][:join]).to eq(Order)
37
+ expect(result[1][1][:join]).to eq(Arel::Table.new(:orders_products))
38
+ expect(result[1][2][:join]).to eq(Product)
39
+ expect(result[1][3][:join]).to eq(Arel::Table.new(:parts_products))
40
+ expect(result[1][4][:join]).to eq(Part)
41
+ end
42
+
43
+ it 'maps part associations' do
44
+ association_graph = {join: part_def.model, on: nil, associations: part_def.associations}
45
+ graph = Clearly::Query::Graph.new(association_graph, :associations)
46
+ result = graph.branches
47
+
48
+ expect(result.size).to eq(1)
49
+ expect(result[0].size).to eq(6)
50
+
51
+ expect(result[0][5][:join]).to eq(Customer)
52
+ expect(result[0][4][:join]).to eq(Order)
53
+ expect(result[0][3][:join]).to eq(Arel::Table.new(:orders_products))
54
+ expect(result[0][2][:join]).to eq(Product)
55
+ expect(result[0][1][:join]).to eq(Arel::Table.new(:parts_products))
56
+ expect(result[0][0][:join]).to eq(Part)
57
+ end
58
+
59
+ it 'maps product associations' do
60
+ association_graph = {join: product_def.model, on: nil, associations: product_def.associations}
61
+ graph = Clearly::Query::Graph.new(association_graph, :associations)
62
+ result = graph.branches
63
+
64
+ expect(result.size).to eq(2)
65
+
66
+ expect(result[0].size).to eq(4)
67
+ expect(result[1].size).to eq(3)
68
+
69
+ expect(result[0][0][:join]).to eq(Product)
70
+ expect(result[0][1][:join]).to eq(Arel::Table.new(:orders_products))
71
+ expect(result[0][2][:join]).to eq(Order)
72
+ expect(result[0][3][:join]).to eq(Customer)
73
+
74
+ expect(result[1][0][:join]).to eq(Product)
75
+ expect(result[1][1][:join]).to eq(Arel::Table.new(:parts_products))
76
+ expect(result[1][2][:join]).to eq(Part)
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Helper do
4
+ include_context 'shared_setup'
5
+
6
+ it 'raises error with one argument for infix operator' do
7
+ expect {
8
+ Clearly::Query::Helper.string_concat_infix('+', 'test')
9
+ }.to raise_error(ArgumentError,"string concatenation requires operator and two or more arguments, given '1'")
10
+ end
11
+
12
+ it 'raises error with no arguments for infix operator' do
13
+ expect {
14
+ Clearly::Query::Helper.string_concat_infix('+')
15
+ }.to raise_error(ArgumentError,"string concatenation requires operator and two or more arguments, given '0'")
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query do
4
+ it 'has a version number' do
5
+ expect(Clearly::Query::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ require 'codeclimate-test-reporter'
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'simplecov'
5
+
6
+ # start code coverage
7
+ SimpleCov.start
8
+
9
+ require 'active_record'
10
+ require 'sqlite3'
11
+ require 'logger'
12
+ require 'zonebie'
13
+ require 'database_cleaner'
14
+
15
+ include ActiveRecord::Tasks
16
+
17
+ require 'clearly/query'
18
+
19
+ # include supporting files
20
+ Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each { |file| require file }
21
+
22
+ RSpec.configure do |config|
23
+ config.expect_with :rspec do |c|
24
+ c.syntax = [:expect]
25
+ end
26
+
27
+ # Run specs in random order to surface order dependencies. If you find an
28
+ # order dependency and want to debug it, you can fix the order by providing
29
+ # the seed, which is printed after each run.
30
+ # --seed 1234
31
+ config.order = 'random'
32
+
33
+ # Zonebie can set this to a random timezone at the beginning of test runs.
34
+ # Specifically for Active Support, it sets Time.zone.
35
+ Zonebie.set_random_timezone
36
+
37
+ app_root = File.expand_path('../..', __FILE__)
38
+ rspec_root = File.join(app_root, 'spec')
39
+ support_root = File.join(rspec_root, 'support')
40
+ db_root = File.join(support_root, 'db')
41
+ migrations_path = File.join(db_root, 'migrate')
42
+ test_db_path = File.join(app_root, 'tmp', 'db', 'test.sqlite3')
43
+
44
+ # if test database exists, delete it
45
+ File.delete(test_db_path) if File.exist?(test_db_path)
46
+
47
+ # configure database
48
+ DatabaseTasks.env = 'test'
49
+ DatabaseTasks.database_configuration = {
50
+ 'test' => {
51
+ adapter: :sqlite3,
52
+ database: test_db_path,
53
+ pool: 5,
54
+ timeout: 5000
55
+ }
56
+ }
57
+ DatabaseTasks.db_dir = File.join rspec_root, 'db'
58
+ #DatabaseTasks.fixtures_path = File.join root, 'test/fixtures'
59
+ DatabaseTasks.migrations_paths = [migrations_path]
60
+ #DatabaseTasks.seed_loader = Seeder.new File.join root, 'db/seeds.rb'
61
+ DatabaseTasks.root = rspec_root
62
+
63
+ # Set the logger for active record
64
+ ActiveRecord::Base.logger = Logger.new(File.join('tmp', 'debug.activerecord.log'))
65
+
66
+ # configure ActiveRecord database
67
+ ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
68
+
69
+ # Connect to db for environment
70
+ ActiveRecord::Base.establish_connection DatabaseTasks.env.to_sym
71
+
72
+ # run migrations
73
+ ActiveRecord::Migrator.migrate(migrations_path)
74
+
75
+ #load 'active_record/railties/databases.rake'
76
+
77
+ config.before(:suite) do
78
+ DatabaseCleaner.strategy = :truncation
79
+ end
80
+
81
+ config.before(:each) do
82
+ DatabaseCleaner.start
83
+ end
84
+
85
+ config.after(:each) do
86
+ DatabaseCleaner.clean
87
+ end
88
+
89
+ end
@@ -0,0 +1,62 @@
1
+ class DbCreate < ActiveRecord::Migration
2
+ def change
3
+ create_table :customers do |t|
4
+ t.string :name, null: false
5
+ t.datetime :last_contact_at, null: false
6
+ t.timestamps null: false
7
+ end
8
+
9
+ create_table :orders do |t|
10
+ t.datetime :shipped_at, null: true
11
+ t.integer :customer_id, null: false
12
+ t.timestamps null: false
13
+ end
14
+
15
+ add_foreign_key :orders, :customers
16
+
17
+ create_table :products do |t|
18
+ t.string :name, null: false
19
+ t.string :code, null: false
20
+ t.string :brand, null: false
21
+ t.datetime :introduced_at, null: false
22
+ t.datetime :discontinued_at, null: true
23
+ t.timestamps null: false
24
+ end
25
+
26
+ create_table :orders_products, id: false do |t|
27
+ t.integer :order_id, null: false
28
+ t.integer :product_id, null: false
29
+ t.timestamps null: false
30
+ end
31
+
32
+ add_foreign_key :orders_products, :customers
33
+ add_foreign_key :orders_products, :orders
34
+
35
+ create_table :parts do |t|
36
+ t.string :name, null: false
37
+ t.string :code, null: false
38
+ t.string :manufacturer, null: false
39
+ t.timestamps null: false
40
+ end
41
+
42
+ create_table :parts_products, id: false do |t|
43
+ t.integer :product_id, null: false
44
+ t.integer :part_id, null: false
45
+ t.timestamps null: false
46
+ end
47
+
48
+ add_foreign_key :parts_products, :products
49
+ add_foreign_key :parts_products, :parts
50
+
51
+ add_index :customers, :name, unique: true
52
+
53
+ add_index :products, :name, unique: true
54
+ add_index :products, :code, unique: true
55
+
56
+ add_index :parts, :name, unique: true
57
+ add_index :parts, :code, unique: true
58
+
59
+ add_index :parts_products, [:product_id, :part_id], unique: true
60
+
61
+ end
62
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_record'
2
+
3
+ class Customer < ActiveRecord::Base
4
+
5
+ has_many :orders, inverse_of: :customer
6
+
7
+ def self.clearly_query_def
8
+ {
9
+ fields: {
10
+ valid: [:name, :last_contact_at],
11
+ text: [:name],
12
+ mappings: [
13
+ {
14
+ name: :title,
15
+ value: Clearly::Query::Helper.string_concat(
16
+ Customer.arel_table[:name],
17
+ Arel::Nodes.build_quoted(' title'))
18
+ }
19
+ ]
20
+ },
21
+ associations: [
22
+ {
23
+ join: Order,
24
+ on: Order.arel_table[:customer_id].eq(Customer.arel_table[:id]),
25
+ available: true,
26
+ associations: [
27
+ {
28
+ join: Arel::Table.new(:orders_products),
29
+ on: Order.arel_table[:id].eq(Arel::Table.new(:orders_products)[:order_id]),
30
+ available: false,
31
+ associations: [
32
+ {
33
+ join: Product,
34
+ on: Product.arel_table[:id].eq(Arel::Table.new(:orders_products)[:product_id]),
35
+ available: true,
36
+ associations: [
37
+ {
38
+ join: Arel::Table.new(:parts_products),
39
+ on: Product.arel_table[:id].eq(Arel::Table.new(:parts_products)[:product_id]),
40
+ available: false,
41
+ associations: [
42
+ {
43
+ join: Part,
44
+ on: Part.arel_table[:id].eq(Arel::Table.new(:parts_products)[:part_id]),
45
+ available: true,
46
+ associations: []
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+ ]
55
+ }
56
+ ],
57
+ defaults: {
58
+ order_by: :created_at,
59
+ direction: :desc
60
+ }
61
+ }
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ require 'active_record'
2
+
3
+ class Order < ActiveRecord::Base
4
+
5
+ belongs_to :customer, inverse_of: :orders
6
+ has_and_belongs_to_many :products
7
+
8
+ def self.clearly_query_def
9
+ {
10
+ fields: {
11
+ valid: [:title, :shipped_at],
12
+ text: [:title],
13
+ mappings: [
14
+ {
15
+ name: :title,
16
+ value: Clearly::Query::Helper.string_concat(
17
+ Customer.arel_table
18
+ .where(Customer.arel_table[:id].eq(Order.arel_table[:customer_id]))
19
+ .project(Customer.arel_table[:name]),
20
+ Arel::Nodes.build_quoted(' ('),
21
+ Arel::Nodes::SqlLiteral.new('CASE WHEN "orders"."shipped_at" IS NULL THEN \'not shipped\' ELSE "orders"."shipped_at" END'),
22
+ Arel::Nodes.build_quoted(')'))
23
+ }
24
+ ]
25
+ },
26
+ associations: [
27
+ {
28
+ join: Customer,
29
+ on: Customer.arel_table[:id].eq(Order.arel_table[:customer_id]),
30
+ available: true
31
+ },
32
+ {
33
+ join: Arel::Table.new(:orders_products),
34
+ on: Order.arel_table[:id].eq(Arel::Table.new(:orders_products)[:order_id]),
35
+ available: false,
36
+ associations: [
37
+ {
38
+ join: Product,
39
+ on: Product.arel_table[:id].eq(Arel::Table.new(:orders_products)[:product_id]),
40
+ available: true,
41
+ associations: [
42
+ {
43
+ join: Arel::Table.new(:parts_products),
44
+ on: Product.arel_table[:id].eq(Arel::Table.new(:parts_products)[:product_id]),
45
+ available: false,
46
+ associations: [
47
+ {
48
+ join: Part,
49
+ on: Part.arel_table[:id].eq(Arel::Table.new(:parts_products)[:part_id]),
50
+ available: true,
51
+ associations: []
52
+ }
53
+ ]
54
+ }
55
+ ]
56
+ }
57
+ ]
58
+ }
59
+ ],
60
+ defaults: {
61
+ order_by: :created_at,
62
+ direction: :desc
63
+ }
64
+ }
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_record'
2
+
3
+ class Part < ActiveRecord::Base
4
+
5
+ has_and_belongs_to_many :products
6
+
7
+ def self.clearly_query_def
8
+ {
9
+ fields: {
10
+ valid: [:title, :name, :code, :manufacturer],
11
+ text: [:title, :name, :code, :manufacturer],
12
+ mappings: [
13
+ {
14
+ name: :title,
15
+ value: Clearly::Query::Helper.string_concat(
16
+ Part.arel_table[:code],
17
+ Arel::Nodes.build_quoted(' '),
18
+ Part.arel_table[:manufacturer])
19
+ }
20
+ ]
21
+ },
22
+ associations: [
23
+ {
24
+ join: Arel::Table.new(:parts_products),
25
+ on: Product.arel_table[:id].eq(Arel::Table.new(:parts_products)[:product_id]),
26
+ available: false,
27
+ associations: [
28
+ {
29
+ join: Product,
30
+ on: Product.arel_table[:id].eq(Arel::Table.new(:orders_products)[:product_id]),
31
+ available: true,
32
+ associations: [
33
+ {
34
+ join: Arel::Table.new(:orders_products),
35
+ on: Order.arel_table[:id].eq(Arel::Table.new(:orders_products)[:order_id]),
36
+ available: false,
37
+ associations: [
38
+ {
39
+ join: Order,
40
+ on: Order.arel_table[:customer_id].eq(Customer.arel_table[:id]),
41
+ available: true,
42
+ associations: [
43
+ {
44
+ join: Customer,
45
+ on: Customer.arel_table[:id].eq(Order.arel_table[:customer_id]),
46
+ available: true
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+ ]
55
+ }
56
+ ],
57
+ defaults: {
58
+ order_by: :name,
59
+ direction: :asc
60
+ }
61
+ }
62
+ end
63
+ end