b_b 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|