parelation 0.0.1
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/.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
|