parelation 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemfiles/4.1.gemfile +3 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +288 -0
- data/Rakefile +4 -0
- data/lib/parelation/applier.rb +60 -0
- data/lib/parelation/criteria/limit.rb +24 -0
- data/lib/parelation/criteria/offset.rb +24 -0
- data/lib/parelation/criteria/order/object.rb +46 -0
- data/lib/parelation/criteria/order.rb +31 -0
- data/lib/parelation/criteria/query.rb +55 -0
- data/lib/parelation/criteria/select.rb +24 -0
- data/lib/parelation/criteria/where/caster.rb +82 -0
- data/lib/parelation/criteria/where/criteria_builder.rb +67 -0
- data/lib/parelation/criteria/where/directional_query_applier.rb +49 -0
- data/lib/parelation/criteria/where.rb +37 -0
- data/lib/parelation/criteria.rb +24 -0
- data/lib/parelation/errors/parameter.rb +2 -0
- data/lib/parelation/helpers.rb +12 -0
- data/lib/parelation/version.rb +3 -0
- data/lib/parelation.rb +19 -0
- data/parelation.gemspec +29 -0
- data/spec/lib/applier_spec.rb +51 -0
- data/spec/lib/criteria/limit_spec.rb +21 -0
- data/spec/lib/criteria/offset_spec.rb +21 -0
- data/spec/lib/criteria/order_spec.rb +36 -0
- data/spec/lib/criteria/query_spec.rb +31 -0
- data/spec/lib/criteria/select_spec.rb +21 -0
- data/spec/lib/criteria/where_spec.rb +157 -0
- data/spec/lib/helpers_spec.rb +21 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/db.rb +37 -0
- data/spec/support/env.rb +14 -0
- metadata +232 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
class Parelation::Criteria::Where::Caster
|
2
|
+
|
3
|
+
# @return [String] an array of values that are considered true
|
4
|
+
#
|
5
|
+
TRUTHY_VALUES = ["1", "t", "true"]
|
6
|
+
|
7
|
+
# @return [String] an array of values that are considered false
|
8
|
+
#
|
9
|
+
FALSY_VALUES = ["0", "f", "false"]
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
#
|
13
|
+
attr_reader :field
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
attr_reader :value
|
18
|
+
|
19
|
+
# @return [Class]
|
20
|
+
#
|
21
|
+
attr_reader :klass
|
22
|
+
|
23
|
+
# @param field [Symbol] the name of the attribute that needs to be casted
|
24
|
+
# @param value [String] the value of the attribute that needs to be casted
|
25
|
+
# @param klass [Class] the corresponding field's class
|
26
|
+
#
|
27
|
+
def initialize(field, value, klass)
|
28
|
+
@field = field.to_s
|
29
|
+
@value = value
|
30
|
+
@klass = klass
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String, Boolean, Time, nil]
|
34
|
+
#
|
35
|
+
def cast
|
36
|
+
case type
|
37
|
+
when :boolean
|
38
|
+
to_boolean
|
39
|
+
when :integer
|
40
|
+
to_integer
|
41
|
+
when :float
|
42
|
+
to_float
|
43
|
+
when :datetime
|
44
|
+
to_time
|
45
|
+
else
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @return [Symbol]
|
53
|
+
#
|
54
|
+
def type
|
55
|
+
klass.columns_hash[field].type
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [true, false, nil]
|
59
|
+
#
|
60
|
+
def to_boolean
|
61
|
+
TRUTHY_VALUES.include?(value) && (return true)
|
62
|
+
FALSY_VALUES.include?(value) && (return false)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Integer]
|
66
|
+
#
|
67
|
+
def to_integer
|
68
|
+
value.to_i
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Float]
|
72
|
+
#
|
73
|
+
def to_float
|
74
|
+
value.to_f
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Time] the parsed time string
|
78
|
+
#
|
79
|
+
def to_time
|
80
|
+
Time.parse(value)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Parelation::Criteria::Where::CriteriaBuilder
|
2
|
+
|
3
|
+
# @return [Hash]
|
4
|
+
#
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
# @return [ActiveRecord::Relation]
|
8
|
+
#
|
9
|
+
attr_reader :chain
|
10
|
+
|
11
|
+
# @param value [Hash] the user-provided criteria
|
12
|
+
# @param chain [ActiveRecord::Relation]
|
13
|
+
#
|
14
|
+
def initialize(value, chain)
|
15
|
+
@value = value
|
16
|
+
@chain = chain
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash] criteria that can be passed into
|
20
|
+
# the +where+ method of an ActiveRecord::Relation chain.
|
21
|
+
#
|
22
|
+
def build
|
23
|
+
value.inject(Hash.new) do |hash, (field, value)|
|
24
|
+
values = [value].flatten
|
25
|
+
|
26
|
+
if values.count > 1
|
27
|
+
assign_array(hash, field, values)
|
28
|
+
else
|
29
|
+
assign_value(hash, field, values)
|
30
|
+
end
|
31
|
+
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Assigns each of the provided values to the +hash+ and casts
|
39
|
+
# the value to a database-readable value.
|
40
|
+
#
|
41
|
+
# @param hash [Hash]
|
42
|
+
# @param field [Symbol]
|
43
|
+
# @param values [Array]
|
44
|
+
#
|
45
|
+
def assign_array(hash, field, values)
|
46
|
+
values.each { |val| (hash[field] ||= []) << cast(field, val) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Assigns the first value of the provided values array
|
50
|
+
# to the +hash+ and casts it to a database-readable value.
|
51
|
+
#
|
52
|
+
# @param hash [Hash]
|
53
|
+
# @param field [Symbol]
|
54
|
+
# @param values [Array]
|
55
|
+
#
|
56
|
+
def assign_value(hash, field, values)
|
57
|
+
hash[field] = cast(field, values[0])
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param field [Symbol]
|
61
|
+
# @param value [String]
|
62
|
+
#
|
63
|
+
def cast(field, value)
|
64
|
+
Parelation::Criteria::Where::Caster
|
65
|
+
.new(field, value, chain.arel_table.engine).cast
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Parelation::Criteria::Where::DirectionalQueryApplier
|
2
|
+
|
3
|
+
# @return [Hash] keyword to operator mappings
|
4
|
+
#
|
5
|
+
OPERATOR_MAPPING = {
|
6
|
+
"where_gt" => ">",
|
7
|
+
"where_gte" => ">=",
|
8
|
+
"where_lt" => "<",
|
9
|
+
"where_lte" => "<="
|
10
|
+
}
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
attr_reader :operator
|
15
|
+
|
16
|
+
# @return [Hash]
|
17
|
+
#
|
18
|
+
attr_reader :criteria
|
19
|
+
|
20
|
+
# @return [ActiveRecord::Relation]
|
21
|
+
#
|
22
|
+
attr_reader :chain
|
23
|
+
|
24
|
+
# @param operator [String] the named operator from the params
|
25
|
+
# @param criteria [Hash] the data to build query operations with
|
26
|
+
# @param chain [ActiveRecord::Relation] the chain to apply to
|
27
|
+
#
|
28
|
+
def initialize(operator, criteria, chain)
|
29
|
+
@operator = operator
|
30
|
+
@criteria = criteria
|
31
|
+
@chain = chain
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [ActiveRecord::Relation] the chain with newly applied operations
|
35
|
+
#
|
36
|
+
def apply
|
37
|
+
criteria.inject(chain) do |chain, (key, value)|
|
38
|
+
chain.where(sql, key, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [String] the base SQL template to build queries on-top of
|
45
|
+
#
|
46
|
+
def sql
|
47
|
+
%Q{"#{chain.arel_table.name}".? #{OPERATOR_MAPPING[operator]} ?}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Parelation::Criteria::Where
|
2
|
+
include Parelation::Criteria
|
3
|
+
|
4
|
+
require_relative "where/caster"
|
5
|
+
require_relative "where/directional_query_applier"
|
6
|
+
require_relative "where/criteria_builder"
|
7
|
+
|
8
|
+
# @param param [String]
|
9
|
+
# @return [true, false]
|
10
|
+
#
|
11
|
+
def self.match?(param)
|
12
|
+
!!(param =~ /^(where|where_(not|gt|gte|lt|lte))$/)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [ActiveRecord::Relation]
|
16
|
+
#
|
17
|
+
def call
|
18
|
+
criteria.inject(chain) do |chain, (field, value)|
|
19
|
+
case param
|
20
|
+
when "where"
|
21
|
+
chain.where(field => value)
|
22
|
+
when "where_not"
|
23
|
+
chain.where.not(field => value)
|
24
|
+
when "where_gt", "where_gte", "where_lt", "where_lte"
|
25
|
+
DirectionalQueryApplier.new(param, criteria, chain).apply
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Hash] containing data used to pass to {#chain}'s +where+ method.
|
33
|
+
#
|
34
|
+
def criteria
|
35
|
+
@criteria ||= CriteriaBuilder.new(value, chain).build
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Parelation::Criteria
|
2
|
+
|
3
|
+
# @return [ActiveRecord::Relation] the current criteria chain
|
4
|
+
#
|
5
|
+
attr_reader :chain
|
6
|
+
|
7
|
+
# @return [String] the param param
|
8
|
+
#
|
9
|
+
attr_reader :param
|
10
|
+
|
11
|
+
# @return [String] the param value
|
12
|
+
#
|
13
|
+
attr_reader :value
|
14
|
+
|
15
|
+
# @param chain [ActiveRecord::Relation]
|
16
|
+
# @param param [String]
|
17
|
+
# @param value [String, Symbol, Array, Hash]
|
18
|
+
#
|
19
|
+
def initialize(chain, param, value)
|
20
|
+
@chain = chain
|
21
|
+
@param = param.clone
|
22
|
+
@value = value.clone
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Parelation::Helpers
|
2
|
+
|
3
|
+
# Shorthand method used in ActionController controllers for converting
|
4
|
+
# and applying parameters to ActionController::Relation criteria chains.
|
5
|
+
#
|
6
|
+
# @param object [ActiveRecord::Relation]
|
7
|
+
# @return [ActiveRecord::Relation]
|
8
|
+
#
|
9
|
+
def parelate(object)
|
10
|
+
Parelation::Applier.new(object, params).apply
|
11
|
+
end
|
12
|
+
end
|
data/lib/parelation.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Parelation
|
2
|
+
module Errors
|
3
|
+
require "parelation/errors/parameter"
|
4
|
+
end
|
5
|
+
|
6
|
+
module Criteria
|
7
|
+
require "parelation/criteria/select"
|
8
|
+
require "parelation/criteria/limit"
|
9
|
+
require "parelation/criteria/offset"
|
10
|
+
require "parelation/criteria/order"
|
11
|
+
require "parelation/criteria/query"
|
12
|
+
require "parelation/criteria/where"
|
13
|
+
end
|
14
|
+
|
15
|
+
require "parelation/applier"
|
16
|
+
require "parelation/criteria"
|
17
|
+
require "parelation/helpers"
|
18
|
+
require "parelation/version"
|
19
|
+
end
|
data/parelation.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "parelation/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "parelation"
|
7
|
+
spec.version = Parelation::VERSION
|
8
|
+
spec.authors = ["Michael van Rooijen"]
|
9
|
+
spec.email = ["michael@vanrooijen.io"]
|
10
|
+
spec.summary = %q{Translates HTTP parameters to ActiveRecord queries.}
|
11
|
+
spec.homepage = "http://meskyanichi.github.io/store_schema/"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "activerecord", ">= 4.1.0"
|
20
|
+
spec.add_development_dependency "bundler"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_development_dependency "mocha"
|
24
|
+
spec.add_development_dependency "sqlite3"
|
25
|
+
spec.add_development_dependency "database_cleaner"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "simplecov"
|
28
|
+
spec.add_development_dependency "yard"
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Applier do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Applier }
|
6
|
+
|
7
|
+
it "should apply the requested criteria" do
|
8
|
+
params = {
|
9
|
+
"format" => :json,
|
10
|
+
"action" => "index",
|
11
|
+
"controller" => "api/v1/tickets",
|
12
|
+
"select" => ["id", "name", "state", "message"],
|
13
|
+
"where" => { state: ["open", "pending"] },
|
14
|
+
"where_not" => { state: "closed" },
|
15
|
+
"where_gt" => { created_at: "2014-01-01T00:00:00Z" },
|
16
|
+
"where_gte" => { updated_at: "2014-01-01T00:00:00Z" },
|
17
|
+
"where_lt" => { created_at: "2014-01-01T01:00:00Z" },
|
18
|
+
"where_lte" => { updated_at: "2014-01-01T01:00:00Z" },
|
19
|
+
"query" => { "ruby on rails" => ["name", "message"] },
|
20
|
+
"order" => ["created_at:desc", "name:asc"],
|
21
|
+
"limit" => "50",
|
22
|
+
"offset" => "100"
|
23
|
+
}
|
24
|
+
|
25
|
+
criteria = klass.new(Project.create.tickets, params).apply
|
26
|
+
ar_query = Project.create.tickets
|
27
|
+
.select(:id, :name, :state, :message)
|
28
|
+
.where(state: ["open", "pending"])
|
29
|
+
.where.not(state: "closed")
|
30
|
+
.where(%Q{"tickets".'created_at' > ?}, "2014-01-01 00:00:00.000000")
|
31
|
+
.where(%Q{"tickets".'updated_at' >= ?}, "2014-01-01 00:00:00.000000")
|
32
|
+
.where(%Q{"tickets".'created_at' < ?}, "2014-01-01 01:00:00.000000")
|
33
|
+
.where(%Q{"tickets".'updated_at' <= ?}, "2014-01-01 01:00:00.000000")
|
34
|
+
.where(
|
35
|
+
%Q{"tickets"."name" LIKE ? OR "tickets"."message" LIKE ?},
|
36
|
+
"%ruby on rails%", "%ruby on rails%"
|
37
|
+
)
|
38
|
+
.order(created_at: :desc, name: :asc)
|
39
|
+
.limit(50)
|
40
|
+
.offset(100)
|
41
|
+
|
42
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raise an exception if parameter data is invalid" do
|
46
|
+
params = { "order" => ["name"] }
|
47
|
+
|
48
|
+
expect { klass.new(Ticket.all, params).apply }
|
49
|
+
.to raise_error(Parelation::Errors::Parameter, "the order parameter is invalid")
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Limit do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Limit }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
expect(klass.match?("limit")).to eq(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not match" do
|
12
|
+
expect(klass.match?("query")).to eq(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add criteria to the chain" do
|
16
|
+
criteria = klass.new(Ticket.all, "limit", "40").call
|
17
|
+
ar_query = Ticket.limit(40)
|
18
|
+
|
19
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Offset do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Offset }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
expect(klass.match?("offset")).to eq(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not match" do
|
12
|
+
expect(klass.match?("query")).to eq(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add criteria to the chain" do
|
16
|
+
criteria = klass.new(Ticket.all, "offset", "40").call
|
17
|
+
ar_query = Ticket.offset(40)
|
18
|
+
|
19
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Order do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Order }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
expect(klass.match?("order")).to eq(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not match" do
|
12
|
+
expect(klass.match?("query")).to eq(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add acending order criteria to the chain" do
|
16
|
+
criteria = klass.new(Ticket.all, "order", "created_at:asc").call
|
17
|
+
ar_query = Ticket.order(created_at: :asc)
|
18
|
+
|
19
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should add descending order criteria to the chain" do
|
23
|
+
criteria = klass.new(Ticket.all, "order", "created_at:desc").call
|
24
|
+
ar_query = Ticket.order(created_at: :desc)
|
25
|
+
|
26
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should combine multiple asc and desc order criteria" do
|
30
|
+
orders = %w[created_at:desc name:asc updated_at:desc message:asc]
|
31
|
+
criteria = klass.new(Ticket.all, "order", orders).call
|
32
|
+
ar_query = Ticket.order(created_at: :desc, name: :asc, updated_at: :desc, message: :asc)
|
33
|
+
|
34
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Query do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Query }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
expect(klass.match?("query")).to eq(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not match" do
|
12
|
+
expect(klass.match?("not_query")).to eq(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add single-column criteria to the chain" do
|
16
|
+
criteria = klass.new(Ticket.all, "query", { "ruby on rails" => "name" }).call
|
17
|
+
ar_query = Ticket.where(%Q{"tickets"."name" LIKE ?}, "%ruby on rails%")
|
18
|
+
|
19
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should add multi-column criteria to the chain" do
|
23
|
+
criteria = klass.new(Ticket.all, "query", { "ruby on rails" => ["name", "message"] }).call
|
24
|
+
ar_query = Ticket.where(
|
25
|
+
%Q{"tickets"."name" LIKE ? OR "tickets"."message" LIKE ?},
|
26
|
+
"%ruby on rails%", "%ruby on rails%"
|
27
|
+
)
|
28
|
+
|
29
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Select do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Select }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
expect(klass.match?("select")).to eq(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not match" do
|
12
|
+
expect(klass.match?("query")).to eq(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add criteria to the chain" do
|
16
|
+
criteria = klass.new(Ticket.all, "select", ["id", "name"]).call
|
17
|
+
ar_query = Ticket.all.select(:id, :name)
|
18
|
+
|
19
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Parelation::Criteria::Where do
|
4
|
+
|
5
|
+
let(:klass) { Parelation::Criteria::Where }
|
6
|
+
|
7
|
+
it "should match" do
|
8
|
+
operators = %w[where where_not where_gt where_gte where_lt where_lte]
|
9
|
+
operators.each { |operator| expect(klass.match?(operator)).to eq(true) }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not match" do
|
13
|
+
expect(klass.match?("query")).to eq(false)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should add = criteria to the chain when argument is a value" do
|
17
|
+
criteria = klass.new(Ticket.all, "where", state: "open").call
|
18
|
+
ar_query = Ticket.where(state: "open")
|
19
|
+
|
20
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add = criteria to the chain when argument is an array" do
|
24
|
+
criteria = klass.new(Ticket.all, "where", state: ["open"]).call
|
25
|
+
ar_query = Ticket.where(state: "open")
|
26
|
+
|
27
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should add IN criteria to the chain when values are many" do
|
31
|
+
criteria = klass.new(Ticket.all, "where", state: ["open", "pending"]).call
|
32
|
+
ar_query = Ticket.where(state: ["open", "pending"])
|
33
|
+
|
34
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should add != criteria to the chain when argument is a value" do
|
38
|
+
criteria = klass.new(Ticket.all, "where_not", state: "open").call
|
39
|
+
ar_query = Ticket.where.not(state: "open")
|
40
|
+
|
41
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should add != criteria to the chain when argument is an array" do
|
45
|
+
criteria = klass.new(Ticket.all, "where_not", state: "open").call
|
46
|
+
ar_query = Ticket.where.not(state: "open")
|
47
|
+
|
48
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should add NOT IN criteria to the chain when values are many" do
|
52
|
+
criteria = klass.new(Ticket.all, "where_not", state: ["open", "pending"]).call
|
53
|
+
ar_query = Ticket.where.not(state: ["open", "pending"])
|
54
|
+
|
55
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
56
|
+
end
|
57
|
+
|
58
|
+
[%w[where_gt >], %w[where_gte >=], %w[where_lt <], %w[where_lte <=]]
|
59
|
+
.each do |(operator, symbol)|
|
60
|
+
|
61
|
+
it "should add #{symbol} criteria to the chain" do
|
62
|
+
criteria = klass.new(Ticket.all, operator, created_at: "2014-08-26T19:20:44Z").call
|
63
|
+
ar_query = Ticket.where(
|
64
|
+
%Q{"tickets".'created_at' #{symbol} ?}, "2014-08-26 19:20:44.000000"
|
65
|
+
)
|
66
|
+
|
67
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should add multiple #{symbol} criteria to the chain" do
|
71
|
+
criteria = klass.new(Ticket.all, operator, created_at: "2014-08-26T19:20:44Z", position: "5").call
|
72
|
+
ar_query = Ticket.where(%Q{"tickets".'created_at' #{symbol} ?}, "2014-08-26 19:20:44.000000")
|
73
|
+
.where(%Q{"tickets".'position' #{symbol} ?}, 5)
|
74
|
+
|
75
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "relations" do
|
80
|
+
|
81
|
+
before do
|
82
|
+
@project1 = Project.create
|
83
|
+
@project2 = Project.create
|
84
|
+
@ticket1 = @project1.tickets.create
|
85
|
+
@ticket2 = @project2.tickets.create
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should yield related resources" do
|
89
|
+
tickets = @project1.tickets
|
90
|
+
expect(tickets.count).to eq(1)
|
91
|
+
expect(tickets.first).to eq(@ticket1)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should yield results when using the same foreign key" do
|
95
|
+
chain = klass.new(@project1.tickets, "where", project_id: @project1.id).call
|
96
|
+
expect(chain.count).to eq(1)
|
97
|
+
expect(chain.first).to eq(@ticket1)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not override foreign key, nor yield results" do
|
101
|
+
chain = klass.new(@project1.tickets, "where", project_id: @project2.id).call
|
102
|
+
expect(chain.count).to eq(0)
|
103
|
+
expect(chain.to_sql).to eq(@project1.tickets.where(project_id: @project2.id).to_sql)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "casting:boolean" do
|
108
|
+
|
109
|
+
["1", "t", "true"].each do |string_bool|
|
110
|
+
it "should cast #{string_bool} to true" do
|
111
|
+
criteria = klass.new(Ticket.all, "where", resolved: string_bool).call
|
112
|
+
ar_query = Ticket.where(resolved: true)
|
113
|
+
|
114
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
["0", "f", "false"].each do |string_bool|
|
119
|
+
it "should cast #{string_bool} to false" do
|
120
|
+
criteria = klass.new(Ticket.all, "where", resolved: string_bool).call
|
121
|
+
ar_query = Ticket.where(resolved: false)
|
122
|
+
|
123
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "casting:integer" do
|
129
|
+
|
130
|
+
it "should cast a string to integer" do
|
131
|
+
criteria = klass.new(Ticket.all, "where", position: "5").call
|
132
|
+
ar_query = Ticket.where(position: 5)
|
133
|
+
|
134
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "casting:float" do
|
139
|
+
|
140
|
+
it "should cast a string to float" do
|
141
|
+
criteria = klass.new(Ticket.all, "where", rating: "2.14").call
|
142
|
+
ar_query = Ticket.where(rating: 2.14)
|
143
|
+
|
144
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "casting:datetime" do
|
149
|
+
|
150
|
+
it "should cast a string to time" do
|
151
|
+
criteria = klass.new(Ticket.all, "where", created_at: "2014-01-01T00:00:00Z").call
|
152
|
+
ar_query = Ticket.where(created_at: '2014-01-01 00:00:00.000000')
|
153
|
+
|
154
|
+
expect(criteria.to_sql).to eq(ar_query.to_sql)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|