b_b 0.1.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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +9 -0
- data/.hound.yml +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.ruby-style.yml +243 -0
- data/.travis.yml +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +212 -0
- data/Rakefile +6 -0
- data/b_b.gemspec +33 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/b_b/builder.rb +81 -0
- data/lib/b_b/component.rb +81 -0
- data/lib/b_b/converter/formula.rb +87 -0
- data/lib/b_b/converter/order.rb +30 -0
- data/lib/b_b/converter/table.rb +49 -0
- data/lib/b_b/converter/value.rb +99 -0
- data/lib/b_b/converter.rb +8 -0
- data/lib/b_b/evaluator/formula.rb +81 -0
- data/lib/b_b/evaluator/table.rb +42 -0
- data/lib/b_b/evaluator/value.rb +72 -0
- data/lib/b_b/evaluator.rb +7 -0
- data/lib/b_b/exception.rb +13 -0
- data/lib/b_b/factory.rb +67 -0
- data/lib/b_b/factory_decorator/extractable.rb +75 -0
- data/lib/b_b/factory_decorator/from.rb +25 -0
- data/lib/b_b/factory_decorator/joinable.rb +15 -0
- data/lib/b_b/factory_decorator/limit.rb +19 -0
- data/lib/b_b/factory_decorator/order.rb +21 -0
- data/lib/b_b/factory_decorator/selectable.rb +15 -0
- data/lib/b_b/factory_decorator.rb +10 -0
- data/lib/b_b/relation.rb +49 -0
- data/lib/b_b/version.rb +3 -0
- data/lib/b_b.rb +58 -0
- metadata +221 -0
data/lib/b_b/builder.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module BB
|
2
|
+
class Builder
|
3
|
+
attr_reader :from, :group, :group_each, :limit, :omit_record_if,
|
4
|
+
:order, :select, :having, :where, :joins
|
5
|
+
|
6
|
+
attr_accessor :options
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@joins = []
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def build
|
14
|
+
structure.flatten.compact.map(&:build).join(" ")
|
15
|
+
end
|
16
|
+
|
17
|
+
def assign(name)
|
18
|
+
get_factory_ivar(name).tap do |factory|
|
19
|
+
append_options!(factory)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def append_joins(name)
|
24
|
+
register_factory(name).tap do |factory|
|
25
|
+
append_options!(factory)
|
26
|
+
joins << factory
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_option_to_just_before_join(rel)
|
31
|
+
return if joins.empty?
|
32
|
+
|
33
|
+
joins.last.options[:rel] = rel
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_offset_to_limit(offset)
|
37
|
+
return if limit.nil?
|
38
|
+
|
39
|
+
limit.options[:offset] = offset
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def structure
|
45
|
+
[select_clause, from, joins, omit_record_if, where, group_clause, having, order, limit]
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_clause
|
49
|
+
select || unless from.nil?
|
50
|
+
get_factory_ivar(:select).tap do |factory|
|
51
|
+
factory.append_formatted_filters(%w(*))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def group_clause
|
57
|
+
group || group_each
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_factory_ivar(name)
|
61
|
+
instance_variable_get("@#{name}") || set_factory_ivar(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_factory_ivar(name)
|
65
|
+
register_factory(name).tap do |factory|
|
66
|
+
instance_variable_set("@#{name}", factory)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def append_options!(factory)
|
71
|
+
return if options.empty?
|
72
|
+
|
73
|
+
factory.options.merge!(options)
|
74
|
+
options.clear
|
75
|
+
end
|
76
|
+
|
77
|
+
def register_factory(name)
|
78
|
+
Factory.build(name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module BB
|
2
|
+
class Component
|
3
|
+
TEMPLATE = {
|
4
|
+
cross_join: "CROSS JOIN %s AS %s",
|
5
|
+
from: "FROM %s",
|
6
|
+
from_union_all_with_alias: "FROM (SELECT * FROM %s) AS %s",
|
7
|
+
from_with_alias: "FROM %s AS %s",
|
8
|
+
full_outer_join_each: "FULL OUTER JOIN EACH %s AS %s ON %s",
|
9
|
+
group: "GROUP BY %s",
|
10
|
+
group_each: "GROUP EACH BY %s",
|
11
|
+
having: "HAVING %s",
|
12
|
+
inner_join: "INNER JOIN %s AS %s ON %s",
|
13
|
+
inner_join_each: "INNER JOIN EACH %s AS %s ON %s",
|
14
|
+
join: "JOIN %s AS %s ON %s",
|
15
|
+
join_each: "JOIN EACH %s AS %s ON %s",
|
16
|
+
left_join: "LEFT JOIN %s AS %s ON %s",
|
17
|
+
left_join_each: "LEFT JOIN EACH %s AS %s ON %s",
|
18
|
+
left_outer_join: "LEFT OUTER JOIN %s AS %s ON %s",
|
19
|
+
left_outer_join_each: "LEFT OUTER JOIN EACH %s AS %s ON %s",
|
20
|
+
limit: "LIMIT %d",
|
21
|
+
limit_with_offset: "LIMIT %d OFFSET %d",
|
22
|
+
omit_record_if: "OMIT RECORD IF %s",
|
23
|
+
order: "ORDER BY %s",
|
24
|
+
right_join: "RIGHT JOIN EACH %s AS %s ON %s",
|
25
|
+
right_join_each: "RIGHT JOIN EACH %s AS %s ON %s",
|
26
|
+
right_outer_join: "RIGHT OUTER JOIN EACH %s AS %s ON %s",
|
27
|
+
right_outer_join_each: "RIGHT OUTER JOIN EACH %s AS %s ON %s",
|
28
|
+
select: "SELECT %s",
|
29
|
+
where: "WHERE %s"
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
attr_accessor :filters, :options, :type
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@filters = []
|
36
|
+
@options = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def build
|
40
|
+
type && send("to_#{type}")
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
(API[:joins] - [:to_cross_join]).each do |name|
|
46
|
+
define_method("to_#{name}") do
|
47
|
+
format(TEMPLATE[name.to_sym], filters[0], options[:as], options[:rel])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
%i(cross_join from_with_alias).each do |name|
|
52
|
+
define_method("to_#{name}") do
|
53
|
+
format(TEMPLATE[name.to_sym], filters[0], options[:as])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
%i(from group group_each order select).each do |name|
|
58
|
+
define_method("to_#{name}") do
|
59
|
+
format(TEMPLATE[name.to_sym], filters.join(", "))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
%i(having omit_record_if where).each do |name|
|
64
|
+
define_method("to_#{name}") do
|
65
|
+
format(TEMPLATE[name.to_sym], filters.join(" "))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_from_union_all_with_alias
|
70
|
+
format(TEMPLATE[:from_union_all_with_alias], filters.join(", "), options[:as])
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_limit
|
74
|
+
format(TEMPLATE[:limit], filters.last)
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_limit_with_offset
|
78
|
+
format(TEMPLATE[:limit_with_offset], filters.last, options[:offset])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module BB
|
2
|
+
module Converter
|
3
|
+
class Formula
|
4
|
+
TEMPLATE = {
|
5
|
+
between: "%s BETWEEN %s",
|
6
|
+
contains: "%s CONTAINS %s",
|
7
|
+
equals: "%s = %s",
|
8
|
+
gt: "%s > %s",
|
9
|
+
gteq: "%s >= %s",
|
10
|
+
in: "%s IN %s",
|
11
|
+
is: "%s IS %s",
|
12
|
+
lt: "%s < %s",
|
13
|
+
lteq: "%s <= %s",
|
14
|
+
match: "REGEXP_MATCH(%s, %s)",
|
15
|
+
not_between: "NOT %s BETWEEN %s",
|
16
|
+
not_contains: "NOT %s CONTAINS %s",
|
17
|
+
not_equals: "%s <> %s",
|
18
|
+
not_gt: "NOT %s > %s",
|
19
|
+
not_gteq: "NOT %s >= %s",
|
20
|
+
not_in: "NOT %s IN %s",
|
21
|
+
not_is: "%s IS NOT %s",
|
22
|
+
not_lt: "NOT %s < %s",
|
23
|
+
not_lteq: "NOT %s <= %s",
|
24
|
+
not_match: "NOT REGEXP_MATCH(%s, %s)"
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
OPERATORS_DICTIONARY = {
|
28
|
+
cont: :contains,
|
29
|
+
contains: :contains,
|
30
|
+
eq: :equals,
|
31
|
+
eql: :equals,
|
32
|
+
equals: :equals,
|
33
|
+
gt: :gt,
|
34
|
+
gteq: :gteq,
|
35
|
+
like: :contains,
|
36
|
+
lt: :lt,
|
37
|
+
lteq: :lteq,
|
38
|
+
not_cont: :not_contains,
|
39
|
+
not_contains: :not_contains,
|
40
|
+
not_eq: :not_equals,
|
41
|
+
not_eql: :not_equals,
|
42
|
+
not_equals: :not_equals,
|
43
|
+
not_gt: :not_gt,
|
44
|
+
not_gteq: :not_gteq,
|
45
|
+
not_like: :not_contains,
|
46
|
+
not_lt: :not_lt,
|
47
|
+
not_lteq: :not_lteq
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
SEARCHABLE_COLUMN_FORMAT = /(?:(\w+)_(not_\w+)|(\w+)_(\w+))/
|
51
|
+
|
52
|
+
attr_reader :column, :operator, :value, :options
|
53
|
+
|
54
|
+
def initialize(column, value, options = {})
|
55
|
+
@column = format_column(column)
|
56
|
+
@value = format_value(value)
|
57
|
+
@options = options
|
58
|
+
end
|
59
|
+
|
60
|
+
def convert
|
61
|
+
evaluated_type = Evaluator::Formula.eval_type(value, options.merge(operator: operator))
|
62
|
+
evaluated_type && format(TEMPLATE[evaluated_type], column, value.convert)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def format_column(column)
|
68
|
+
scanned_column = column.to_s.scan(SEARCHABLE_COLUMN_FORMAT).flatten.compact
|
69
|
+
!scanned_column.empty? && format_operator(scanned_column[1]) ? scanned_column[0] : column
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_operator(operator)
|
73
|
+
@operator = OPERATORS_DICTIONARY[operator.to_sym]
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_value(value)
|
77
|
+
value.is_a?(Value) ? value : Value.new(value)
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
def convert(column, value, options = {})
|
82
|
+
new(column, value, options).convert
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module BB
|
2
|
+
module Converter
|
3
|
+
class Order
|
4
|
+
TEMPLATE = "%s %s".freeze
|
5
|
+
|
6
|
+
attr_reader :column, :sort_key
|
7
|
+
|
8
|
+
def initialize(column, options = {})
|
9
|
+
@column = column
|
10
|
+
@sort_key = format_sort_key(options[:sort_key])
|
11
|
+
end
|
12
|
+
|
13
|
+
def convert
|
14
|
+
format(TEMPLATE, column, sort_key)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def format_sort_key(sort_key)
|
20
|
+
sort_key.to_s.casecmp("DESC").zero? ? :DESC : :ASC
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def convert(column, options = {})
|
25
|
+
new(column, options).convert
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module BB
|
2
|
+
module Converter
|
3
|
+
class Table
|
4
|
+
TEMPLATE = {
|
5
|
+
subquery: "(%s)",
|
6
|
+
table_date_range: "TABLE_DATE_RANGE(%s, %s, %s)"
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
attr_reader :value, :options
|
10
|
+
|
11
|
+
def initialize(value, options = {})
|
12
|
+
@value = value
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def convert
|
17
|
+
evaluated_type = Evaluator::Table.eval_type(value, options)
|
18
|
+
evaluated_type && send("to_#{evaluated_type}")
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def to_subquery
|
24
|
+
format(TEMPLATE[:subquery], value.to_sql)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_table_date
|
28
|
+
options[:on].strftime("#{value}%Y%m%d")
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_table_date_range
|
32
|
+
from = Value.convert(options[:from], type: :timestamp)
|
33
|
+
to = Value.convert(options[:to], type: :timestamp)
|
34
|
+
|
35
|
+
format(TEMPLATE[:table_date_range], value, from, to)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_plain
|
39
|
+
value.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def convert(value, options = {})
|
44
|
+
new(value, options).convert
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module BB
|
2
|
+
module Converter
|
3
|
+
class Value
|
4
|
+
TEMPLATE = {
|
5
|
+
array: "(%s)",
|
6
|
+
date: "DATE('%Y-%m-%d')",
|
7
|
+
null: "NULL",
|
8
|
+
range: "%s AND %s",
|
9
|
+
regexp: "r'%s'",
|
10
|
+
string: "'%s'",
|
11
|
+
subquery: "(%s)",
|
12
|
+
time: "TIMESTAMP('%Y-%m-%d %H:%M:%S')",
|
13
|
+
timestamp: "TIMESTAMP('%Y-%m-%d')"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
attr_reader :value, :options, :type
|
17
|
+
|
18
|
+
def initialize(value, options = {})
|
19
|
+
@value = value
|
20
|
+
@options = options
|
21
|
+
@type = format_type
|
22
|
+
end
|
23
|
+
|
24
|
+
def convert
|
25
|
+
send("to_#{type}")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def format_type
|
31
|
+
options[:type] || Evaluator::Value.eval_type(value, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_array
|
35
|
+
format(TEMPLATE[:array], value.map(&method(:convert_member_value)).join(", "))
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_boolean
|
39
|
+
value.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method(:to_numeric, :to_boolean)
|
43
|
+
|
44
|
+
def to_date
|
45
|
+
value.strftime(TEMPLATE[:date])
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_time
|
49
|
+
value.strftime(TEMPLATE[:time])
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_timestamp
|
53
|
+
value.strftime(TEMPLATE[:timestamp])
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_null
|
57
|
+
TEMPLATE[:null]
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_range
|
61
|
+
range_values = extract_range_values(value).map(&method(:convert_member_value))
|
62
|
+
format(TEMPLATE[:range], range_values[0], range_values[1])
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_regexp
|
66
|
+
format(TEMPLATE[:regexp], value.inspect[1..-2])
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_string
|
70
|
+
format(TEMPLATE[:string], value)
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_subquery
|
74
|
+
format(TEMPLATE[:subquery], value.to_sql)
|
75
|
+
end
|
76
|
+
|
77
|
+
def convert_member_value(value)
|
78
|
+
self.class.convert(value, eval_types: %i(boolean date null numeric string time))
|
79
|
+
end
|
80
|
+
|
81
|
+
def extract_range_values(value)
|
82
|
+
[].tap do |arr|
|
83
|
+
arr << value.begin
|
84
|
+
arr << if value.exclude_end?
|
85
|
+
(!value.end.is_a?(Time) ? value.last(1)[0] : value.last - 1)
|
86
|
+
else
|
87
|
+
value.end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
def convert(value, options = {})
|
94
|
+
new(value, options).convert
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module BB
|
2
|
+
module Evaluator
|
3
|
+
class Formula
|
4
|
+
EVALUATE_TYPES = %i(between contains equals gt gteq in is lt lteq match).freeze
|
5
|
+
|
6
|
+
attr_reader :value, :operator, :negation
|
7
|
+
|
8
|
+
def initialize(value, options = {})
|
9
|
+
@value = value
|
10
|
+
@operator = options[:operator]
|
11
|
+
@negation = options[:negation]
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval_type
|
15
|
+
evaluated_type = EVALUATE_TYPES.detect { |type| send("#{type}?") }
|
16
|
+
|
17
|
+
if !evaluated_type.nil? && negative? && !double_negative?
|
18
|
+
:"not_#{evaluated_type}"
|
19
|
+
else
|
20
|
+
evaluated_type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def negative?
|
27
|
+
negation || operator.to_s.start_with?("not")
|
28
|
+
end
|
29
|
+
|
30
|
+
def double_negative?
|
31
|
+
negation && operator.to_s.start_with?("not")
|
32
|
+
end
|
33
|
+
|
34
|
+
def between?
|
35
|
+
value.type == :range
|
36
|
+
end
|
37
|
+
|
38
|
+
def contains?
|
39
|
+
%i(contains not_contains).include?(operator)
|
40
|
+
end
|
41
|
+
|
42
|
+
def equals?
|
43
|
+
%i(date numeric string time).include?(value.type) && (operator.nil? || %i(equals not_equals).include?(operator))
|
44
|
+
end
|
45
|
+
|
46
|
+
def gt?
|
47
|
+
%i(gt not_gt).include?(operator)
|
48
|
+
end
|
49
|
+
|
50
|
+
def gteq?
|
51
|
+
%i(gteq not_gteq).include?(operator)
|
52
|
+
end
|
53
|
+
|
54
|
+
def in?
|
55
|
+
%i(array subquery).include?(value.type)
|
56
|
+
end
|
57
|
+
|
58
|
+
def is?
|
59
|
+
%i(boolean null).include?(value.type)
|
60
|
+
end
|
61
|
+
|
62
|
+
def lt?
|
63
|
+
%i(lt not_lt).include?(operator)
|
64
|
+
end
|
65
|
+
|
66
|
+
def lteq?
|
67
|
+
%i(lteq not_lteq).include?(operator)
|
68
|
+
end
|
69
|
+
|
70
|
+
def match?
|
71
|
+
value.type == :regexp
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def eval_type(value, options = {})
|
76
|
+
new(value, options).eval_type
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BB
|
2
|
+
module Evaluator
|
3
|
+
class Table
|
4
|
+
EVALUATE_TYPES = %i(subquery table_date table_date_range plain).freeze
|
5
|
+
|
6
|
+
attr_reader :value, :options
|
7
|
+
|
8
|
+
def initialize(value, options = {})
|
9
|
+
@value = value
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def eval_type
|
14
|
+
EVALUATE_TYPES.detect { |type| send("#{type}?") }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def subquery?
|
20
|
+
value.respond_to?(:to_sql)
|
21
|
+
end
|
22
|
+
|
23
|
+
def table_date?
|
24
|
+
!value.to_s.empty? && options[:on].respond_to?(:strftime)
|
25
|
+
end
|
26
|
+
|
27
|
+
def table_date_range?
|
28
|
+
!value.to_s.empty? && options[:from].respond_to?(:strftime) && options[:to].respond_to?(:strftime)
|
29
|
+
end
|
30
|
+
|
31
|
+
def plain?
|
32
|
+
!value.to_s.empty? && !table_date? && !table_date_range? && !subquery?
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def eval_type(value, options = {})
|
37
|
+
new(value, options).eval_type
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module BB
|
2
|
+
module Evaluator
|
3
|
+
class Value
|
4
|
+
EVALUATE_TYPES = %i(array boolean date null numeric range regexp string subquery time).freeze
|
5
|
+
|
6
|
+
attr_reader :value, :options
|
7
|
+
|
8
|
+
def initialize(value, options = {})
|
9
|
+
@value = value
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def eval_type
|
14
|
+
eval_types.detect { |type| send("#{type}?") }.tap do |type|
|
15
|
+
raise UnevaluableTypeError, "unevaluable type of value: #{value} (#{value.class})" if type.nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def eval_types
|
22
|
+
options[:eval_types] || EVALUATE_TYPES
|
23
|
+
end
|
24
|
+
|
25
|
+
def array?
|
26
|
+
value.is_a?(Array) && !value.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def boolean?
|
30
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
31
|
+
end
|
32
|
+
|
33
|
+
def date?
|
34
|
+
value.respond_to?(:strftime) && !value.respond_to?(:hour)
|
35
|
+
end
|
36
|
+
|
37
|
+
def null?
|
38
|
+
value.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def numeric?
|
42
|
+
value.is_a?(Numeric)
|
43
|
+
end
|
44
|
+
|
45
|
+
def range?
|
46
|
+
value.is_a?(Range) && !value.size.is_a?(Float)
|
47
|
+
end
|
48
|
+
|
49
|
+
def regexp?
|
50
|
+
value.is_a?(Regexp)
|
51
|
+
end
|
52
|
+
|
53
|
+
def string?
|
54
|
+
value.is_a?(String) || value.is_a?(Symbol)
|
55
|
+
end
|
56
|
+
|
57
|
+
def subquery?
|
58
|
+
value.respond_to?(:to_sql)
|
59
|
+
end
|
60
|
+
|
61
|
+
def time?
|
62
|
+
value.respond_to?(:strftime) && value.respond_to?(:hour)
|
63
|
+
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
def eval_type(value, options = {})
|
67
|
+
new(value, options).eval_type
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module BB
|
2
|
+
# A general BB exception
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Raised when using relation methods without calling required arguments.
|
6
|
+
class ArgumentError < Error; end
|
7
|
+
|
8
|
+
# Raised when behavior is not implemented, usually used in an abstract class.
|
9
|
+
class NotImplementedError < Error; end
|
10
|
+
|
11
|
+
# Raised when using evaluation methods
|
12
|
+
class UnevaluableTypeError < Error; end
|
13
|
+
end
|