Class: CafeCar::QueryBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/cafe_car/query_builder.rb

Defined Under Namespace

Classes: Op

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope) ⇒ QueryBuilder

Returns a new instance of QueryBuilder.



29
30
31
# File 'lib/cafe_car/query_builder.rb', line 29

def initialize(scope)
  @scope = scope
end

Instance Attribute Details

#scopeObject (readonly)

Returns the value of attribute scope.



27
28
29
# File 'lib/cafe_car/query_builder.rb', line 27

def scope
  @scope
end

Instance Method Details

#arel(key) ⇒ Object



34
# File 'lib/cafe_car/query_builder.rb', line 34

def arel(key)  = @scope.arel_table[chomp(key)]

#arel!(node) ⇒ Object



102
# File 'lib/cafe_car/query_builder.rb', line 102

def arel!(node) = @scope.where!(node)

#association!(name, value) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/cafe_car/query_builder.rb', line 133

def association!(name, value, ...)
  update! do
    case value
    when true  then @scope.where_assoc_exists(name)
    when false then @scope.where_assoc_not_exists(name)
    when Integer, Range, /^\d+$/
      @scope.where_assoc_count(parse(name, value), :==, name)
    when Op
      value = parse(name, value)
      @scope.where_assoc_count(value.rhs, value.flop, name)
    else @scope.where_assoc_exists(name) { all.query!(value, ...) }
    end
  end
end

#association?(name) ⇒ Boolean

Returns:

  • (Boolean)


98
# File 'lib/cafe_car/query_builder.rb', line 98

def association?(name) = reflection(name).present?

#attribute!(key, value) ⇒ Object



123
124
125
126
127
128
129
130
131
# File 'lib/cafe_car/query_builder.rb', line 123

def attribute!(key, value)
  case [ key, value ]
  in _, Regexp
    @scope.where!(arel(key).matches_regexp(value.source, !value.casefold?))
  in _, Op
    @scope.where!(parse(key, value).arel(arel(key)))
  else @scope.where!(key => parse(key, value))
  end
end

#attribute?(name) ⇒ Boolean

Returns:

  • (Boolean)


99
# File 'lib/cafe_car/query_builder.rb', line 99

def attribute?(name)   = column(name).present?

#chomp(key) ⇒ Object



35
# File 'lib/cafe_car/query_builder.rb', line 35

def chomp(key) = key.to_s.sub(/\W+$/, "")

#column(name) ⇒ Object



96
# File 'lib/cafe_car/query_builder.rb', line 96

def column(name)       = @scope.columns_hash[name.to_s]

#not!Object



91
92
93
94
# File 'lib/cafe_car/query_builder.rb', line 91

def not!(&)
  inverted = unscoped.tap { _1.instance_exec(&) }.scope.invert_where
  update! { _1.and(inverted) }
end

#param!(key, value) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/cafe_car/query_builder.rb', line 104

def param!(key, value)
  case key
  when /^(.*?)\s*!$/
    not! { param!($1, value) }
  when /^(.*?)\s*~$/
    param!($1, Regexp.new(value, Regexp::IGNORECASE))
  when /^(.*?)\s*([<>]=?)$/
    param!($1, Op.new($2, value))
  when method(:association?)
    association!(key, value)
  when method(:attribute?)
    attribute!(key, value)
  when method(:scope?)
    scope!(key, value)
  else
    raise MissingAttributeError, "can't find #{key.inspect} on #{@scope.model_name}"
  end
end

#parse(key, value) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/cafe_car/query_builder.rb', line 49

def parse(key, value)
  new_value = parse_value(key, value)
  if new_value != value
    parse(key, new_value)
  else
    new_value
  end
end

#parse_range(key, value) ⇒ Object



43
44
45
46
47
# File 'lib/cafe_car/query_builder.rb', line 43

def parse_range(key, value)
  Range.new(parse_value(key, value.begin).then { _1.try(:begin) or _1 },
            parse_value(key, value.end).then { value.exclude_end? ? _1.try(:begin) : _1.try(:end) or _1 },
            value.exclude_end?)
end

#parse_time(value) ⇒ Object



37
38
39
40
41
# File 'lib/cafe_car/query_builder.rb', line 37

def parse_time(value)
  Chronic.parse(value, guess: false, context: :past)
rescue NoMethodError
  nil
end

#parse_value(key, value) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/cafe_car/query_builder.rb', line 58

def parse_value(key, value)
  case value
  in Op(rhs: /^=(.*)$/)
    Op.new("#{value.op}=", $1)
  in Op(op: (:< | :>=), rhs: Range)
    value.map(&:begin)
  in Op(op: (:> | :<=), rhs: Range)
    value.map(&:end)
  in Range
    parse_range(key, value)
  in Array | Op
    value.map { parse_value(key, _1) }
  in "true" then true
  in "false" then false
  in String
    case column(key)&.type || reflection(key)&.macro
    when :datetime then parse_time(value) || value
    when :integer  then value.to_i
    when :float    then value.to_f
    when :belongs_to, :has_many, :has_one
      value.to_i
    else value
    end
  else value
  end
end

#queryObject



174
# File 'lib/cafe_car/query_builder.rb', line 174

def query(...) = clone.update!(&:all).query!(...)

#query!(params = nil) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/cafe_car/query_builder.rb', line 162

def query!(params = nil)
  case params
  when Hash  then params.each { param!(_1, _2) }
  when Array then params.each { query! _1 }
  when String then search!(params)
  # when Arel::Nodes::Node then arel!(params)
  when nil
  else raise ArgumentError, "cannot query on #{params}"
  end
  self
end

#reflection(name) ⇒ Object



97
# File 'lib/cafe_car/query_builder.rb', line 97

def reflection(name)   = @scope.reflect_on_association(name)

#scope!(name, value) ⇒ Object



148
149
150
151
152
153
154
# File 'lib/cafe_car/query_builder.rb', line 148

def scope!(name, value)
  value = parse_value(name, value)
  arity = (@scope.scopes[name] || @scope.method(name)).arity
  value = nil if arity == 0 and value == true

  update! { _1.public_send(name, *value) }
end

#scope?(name) ⇒ Boolean

Returns:

  • (Boolean)


100
# File 'lib/cafe_car/query_builder.rb', line 100

def scope?(name)       = name.intern.in? @scope.local_methods

#search!(term) ⇒ Object



156
157
158
159
160
# File 'lib/cafe_car/query_builder.rb', line 156

def search!(term)
  @scope.search!(term) if @scope.respond_to?(:search!)
  @scope.query!("body~": term) if @scope < ::ActionText::RichText
  update! { _1.search(term) }
end

#unscopedObject



33
# File 'lib/cafe_car/query_builder.rb', line 33

def unscoped   = QueryBuilder.new(@scope.unscope(:where))

#update!Object



85
86
87
88
89
# File 'lib/cafe_car/query_builder.rb', line 85

def update!(&)
  scope  = yield @scope
  @scope = scope if scope
  self
end