groovy 0.4.1 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/groovy.rb +8 -0
- data/lib/groovy/model.rb +17 -15
- data/lib/groovy/query.rb +47 -20
- data/lib/groovy/schema.rb +15 -4
- data/lib/groovy/version.rb +1 -1
- data/spec/model_spec.rb +9 -1
- data/spec/query_spec.rb +13 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2569117dfa3663d6c60ed950be2972c58116e7eaf6d21f5c6640cb91642fee4f
|
4
|
+
data.tar.gz: 33e1651825dd626dab979e9c504d5de1b7d8162363d42cbeca43873e370cad04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66b311fe79a3f9c8467ddbe9f863a2fd731b9c7dd9df2dbeb49fc1b8d62f4b7b506b2e0ca1f05c6ad7766ede89df0a26cb4a8eae8f96097ff613ab17cadde593
|
7
|
+
data.tar.gz: c1dbb4f84dfdb79d870a0e498acbc63d7e1268f5cfd1f0951fd16cc7d890b68a6d3679e903eb703b8de4a44b6765070594df0eb53b8cfbeb7ac9fb1499f5dd3f
|
data/lib/groovy.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'groonga'
|
2
2
|
require File.expand_path(File.dirname(__FILE__)) + '/groovy/model'
|
3
3
|
|
4
|
+
# overwrite Groonga::Record#inspect because the #attributes part is
|
5
|
+
# making debugging take ages
|
6
|
+
class Groonga::Record
|
7
|
+
def inspect
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
module Groovy
|
5
13
|
|
6
14
|
class Error < StandardError; end
|
data/lib/groovy/model.rb
CHANGED
@@ -187,16 +187,6 @@ module Groovy
|
|
187
187
|
query
|
188
188
|
end
|
189
189
|
|
190
|
-
def first(num = 1)
|
191
|
-
arr = limit(num)
|
192
|
-
num == 1 ? arr.first : arr
|
193
|
-
end
|
194
|
-
|
195
|
-
def last(num = 1)
|
196
|
-
arr = all.sort_by(_id: :desc).limit(num)
|
197
|
-
num == 1 ? arr.first : arr
|
198
|
-
end
|
199
|
-
|
200
190
|
def query
|
201
191
|
query_class.new(self, table)
|
202
192
|
end
|
@@ -208,7 +198,7 @@ module Groovy
|
|
208
198
|
end
|
209
199
|
end
|
210
200
|
|
211
|
-
[:find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
201
|
+
[:first, :last, :select, :find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
212
202
|
define_method scope_method do |*args, &block|
|
213
203
|
query.public_send(scope_method, *args, &block)
|
214
204
|
end
|
@@ -221,6 +211,12 @@ module Groovy
|
|
221
211
|
set_timestamp(attributes, :created_at)
|
222
212
|
set_timestamp(attributes, :updated_at)
|
223
213
|
|
214
|
+
# remove nil attributes for integer columns, otherwise
|
215
|
+
# we get a TypeError (no implicit conversion from nil to integer)
|
216
|
+
attributes.each do |k, v|
|
217
|
+
attributes.delete(k) if v.nil? # && schema.integer_columns.include?(k)
|
218
|
+
end
|
219
|
+
|
224
220
|
if table.support_key?
|
225
221
|
raise "Key required" if key.nil?
|
226
222
|
table.add(key, attributes)
|
@@ -297,7 +293,7 @@ module Groovy
|
|
297
293
|
else
|
298
294
|
attrs ||= {}
|
299
295
|
unless attrs.is_a?(Hash)
|
300
|
-
raise ArgumentError.new("Attributes should be a Hash")
|
296
|
+
raise ArgumentError.new("Attributes should be a Hash, not a #{attrs.class}")
|
301
297
|
end
|
302
298
|
|
303
299
|
# don't call set_attributes since we don't want to call
|
@@ -309,6 +305,12 @@ module Groovy
|
|
309
305
|
@changes = {}
|
310
306
|
end
|
311
307
|
|
308
|
+
# get reference to the actual record in the Groonga table,
|
309
|
+
# not the temporary one we get as part of a search result.
|
310
|
+
def load_record
|
311
|
+
self.class.table[id]
|
312
|
+
end
|
313
|
+
|
312
314
|
def inspect
|
313
315
|
"#<#{self.class.name} id:#{id.inspect} attributes:[#{self.class.attribute_names.join(', ')}]>"
|
314
316
|
end
|
@@ -394,18 +396,18 @@ module Groovy
|
|
394
396
|
end
|
395
397
|
|
396
398
|
def ==(other)
|
397
|
-
self.id == other.id
|
399
|
+
self.class == other.class && self.id == other.id
|
398
400
|
end
|
399
401
|
|
400
402
|
def <=>(other)
|
401
|
-
self.id <=> other.id
|
403
|
+
self.class == other.class && self.id <=> other.id
|
402
404
|
end
|
403
405
|
|
404
406
|
private
|
405
407
|
|
406
408
|
def get_record_attribute(key)
|
407
409
|
val = record[key]
|
408
|
-
if self.class.schema.
|
410
|
+
if self.class.schema.time_columns.include?(key)
|
409
411
|
fix_time_value(val)
|
410
412
|
else
|
411
413
|
val
|
data/lib/groovy/query.rb
CHANGED
@@ -3,9 +3,11 @@ module Groovy
|
|
3
3
|
class Query
|
4
4
|
|
5
5
|
include Enumerable
|
6
|
+
|
6
7
|
AND = '+'.freeze
|
7
8
|
NOT = '-'.freeze
|
8
9
|
PER_PAGE = 50.freeze
|
10
|
+
# ESCAPE_CHARS_REGEX = /([\(\)\/\\])/.freeze
|
9
11
|
VALID_QUERY_CHARS = 'a-zA-Z0-9_\.,&-'.freeze
|
10
12
|
REMOVE_INVALID_CHARS_REGEX = Regexp.new('[^\s' + VALID_QUERY_CHARS + ']').freeze
|
11
13
|
|
@@ -47,6 +49,11 @@ module Groovy
|
|
47
49
|
self
|
48
50
|
end
|
49
51
|
|
52
|
+
def select(&block)
|
53
|
+
@select_block = block
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
50
57
|
def find(id)
|
51
58
|
find_by(_id: id)
|
52
59
|
end
|
@@ -76,7 +83,7 @@ module Groovy
|
|
76
83
|
add_param(AND + [key, val.max].join(':<=')) if val.max # gte
|
77
84
|
|
78
85
|
elsif val.is_a?(Regexp)
|
79
|
-
str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
86
|
+
str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
80
87
|
param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
|
81
88
|
add_param(AND + [key, str.downcase].join(param)) # regex must be downcase
|
82
89
|
|
@@ -110,7 +117,7 @@ module Groovy
|
|
110
117
|
add_param(AND + [key, val.max].join(':>=')) if val.max # lte, nil if range.max is -1
|
111
118
|
|
112
119
|
elsif val.is_a?(Regexp)
|
113
|
-
str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
120
|
+
str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
114
121
|
param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
|
115
122
|
add_param(NOT + [key, str.downcase].join(param)) # regex must be downcase
|
116
123
|
|
@@ -132,25 +139,25 @@ module Groovy
|
|
132
139
|
end
|
133
140
|
|
134
141
|
def limit(num)
|
135
|
-
|
142
|
+
sorting[:limit] = num
|
136
143
|
self
|
137
144
|
end
|
138
145
|
|
139
146
|
def offset(num)
|
140
|
-
|
147
|
+
sorting[:offset] = num
|
141
148
|
self
|
142
149
|
end
|
143
150
|
|
144
|
-
def paginate(page = 1)
|
151
|
+
def paginate(page = 1, per_page: PER_PAGE)
|
145
152
|
page = 1 if page.to_i < 1
|
146
|
-
offset = ((page.to_i)-1) *
|
147
|
-
offset(offset).limit(
|
153
|
+
offset = ((page.to_i)-1) * per_page
|
154
|
+
offset(offset).limit(per_page) # returns self
|
148
155
|
end
|
149
156
|
|
150
157
|
# sort_by(title: :asc)
|
151
158
|
def sort_by(hash)
|
152
|
-
if hash.is_a?(String) || hash.is_a?(Symbol) # e.g. 'title.desc' or :title (asc by default)
|
153
|
-
param, dir = hash.to_s.split(
|
159
|
+
if hash.is_a?(String) || hash.is_a?(Symbol) # e.g. 'title.desc', 'title desc' or :title (asc by default)
|
160
|
+
param, dir = hash.to_s.split(/\s|\./)
|
154
161
|
hash = {}
|
155
162
|
hash[param] = dir || 'asc'
|
156
163
|
end
|
@@ -201,23 +208,30 @@ module Groovy
|
|
201
208
|
@total_entries
|
202
209
|
end
|
203
210
|
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
211
|
+
def first(num = 1)
|
212
|
+
limit(num)
|
213
|
+
num == 1 ? records.first : records
|
214
|
+
end
|
215
|
+
|
216
|
+
def last(num = 1)
|
217
|
+
if sorting[:by]
|
218
|
+
get_last(num)
|
219
|
+
else # no sorting, so
|
220
|
+
sort_by(_id: :desc).limit(num)
|
221
|
+
# if last(2) or more, then re-sort by ascending ID
|
222
|
+
num == 1 ? records.first : records.sort { |a,b| a.id <=> b.id }
|
209
223
|
end
|
210
224
|
end
|
211
225
|
|
212
226
|
def in_batches(of: 1000, from: nil, &block)
|
213
|
-
|
214
|
-
|
227
|
+
sorting[:limit] = of
|
228
|
+
sorting[:offset] = from || 0
|
215
229
|
|
216
230
|
while results.any?
|
217
231
|
yield to_a
|
218
232
|
break if results.size < of
|
219
233
|
|
220
|
-
|
234
|
+
sorting[:offset] += of
|
221
235
|
@records = @results = nil # reset
|
222
236
|
end
|
223
237
|
end
|
@@ -229,9 +243,18 @@ module Groovy
|
|
229
243
|
end
|
230
244
|
|
231
245
|
private
|
232
|
-
attr_reader :model, :table, :options
|
246
|
+
attr_reader :model, :table, :options, :select_block
|
247
|
+
|
248
|
+
def get_last(count = 1)
|
249
|
+
if count > 1
|
250
|
+
records[(size-count)..-1]
|
251
|
+
else
|
252
|
+
records[size-1]
|
253
|
+
end
|
254
|
+
end
|
233
255
|
|
234
256
|
def add_param(param)
|
257
|
+
raise "Select block already given!" if select_block
|
235
258
|
raise "Duplicate param: #{param}" if parameters.include?(param)
|
236
259
|
parameters.push(param)
|
237
260
|
end
|
@@ -244,7 +267,10 @@ module Groovy
|
|
244
267
|
end
|
245
268
|
|
246
269
|
def execute
|
247
|
-
set = if
|
270
|
+
set = if select_block
|
271
|
+
debug "Finding records with select block"
|
272
|
+
table.select { |record| select_block.call(record) }
|
273
|
+
elsif parameters.any?
|
248
274
|
query = prepare_query
|
249
275
|
debug "Finding records with query: #{query}"
|
250
276
|
table.select(query, options)
|
@@ -255,7 +281,7 @@ module Groovy
|
|
255
281
|
|
256
282
|
@total_entries = set.size
|
257
283
|
|
258
|
-
debug "Sorting with #{sort_key_and_order}
|
284
|
+
debug "Sorting with #{sort_key_and_order} (options: #{sorting.inspect})"
|
259
285
|
set = set.sort(sort_key_and_order, {
|
260
286
|
limit: sorting[:limit],
|
261
287
|
offset: sorting[:offset], # [sorting[:offset], @total_entries].min
|
@@ -287,6 +313,7 @@ module Groovy
|
|
287
313
|
query = parameters.join(' ').split(/ or /i).map do |part|
|
288
314
|
part.gsub(' ', ' ') # replace double with single spaces
|
289
315
|
.gsub(space_regex, '\ \1') # escape spaces before word letters
|
316
|
+
.gsub('_slash_', '\/').gsub('_openp_', '\(').gsub('_closep_', '\)')
|
290
317
|
.gsub(/(\d\d):(\d\d):(\d\d)/, '\1\:\2\:\3') # escape hh:mm:ss in timestamps
|
291
318
|
end.join(' OR ').sub(/^-/, '_id:>0 -') #.gsub(' OR -', ' -')
|
292
319
|
end
|
data/lib/groovy/schema.rb
CHANGED
@@ -56,14 +56,25 @@ module Groovy
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def time_columns
|
59
|
-
|
60
|
-
get_names(table.columns.select { |c| c.column? && c.range.name == 'Time' })
|
59
|
+
columns_by_type('Time')
|
61
60
|
end
|
62
61
|
|
63
|
-
def
|
64
|
-
|
62
|
+
def integer_columns
|
63
|
+
columns_by_type('Int32')
|
65
64
|
end
|
66
65
|
|
66
|
+
def boolean_columns
|
67
|
+
columns_by_type('Bool')
|
68
|
+
end
|
69
|
+
|
70
|
+
def columns_by_type(type)
|
71
|
+
get_names(table.columns.select { |c| c.column? && c.range.name == type })
|
72
|
+
end
|
73
|
+
|
74
|
+
# def time_column?(name)
|
75
|
+
# time_columns.include?(name)
|
76
|
+
# end
|
77
|
+
|
67
78
|
def rebuild!
|
68
79
|
log("Rebuilding!")
|
69
80
|
# remove_table! if table
|
data/lib/groovy/version.rb
CHANGED
data/spec/model_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe Groovy::Model do
|
|
17
17
|
describe '.scope' do
|
18
18
|
|
19
19
|
before :all do
|
20
|
-
TestProduct.class_eval do
|
20
|
+
TestProduct.class_eval do
|
21
21
|
scope :with_name, -> (name) { where(name: name) if name }
|
22
22
|
scope :by_price_asc, -> { sort_by(price: :asc) }
|
23
23
|
scope :cheapest, -> { by_price_asc }
|
@@ -49,6 +49,13 @@ describe Groovy::Model do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
describe '.create' do
|
52
|
+
|
53
|
+
it 'does not explode when inserting nil values for columns' do
|
54
|
+
expect do
|
55
|
+
TestProduct.create({ price: nil })
|
56
|
+
end.not_to raise_error
|
57
|
+
end
|
58
|
+
|
52
59
|
end
|
53
60
|
|
54
61
|
describe '.find' do
|
@@ -60,6 +67,7 @@ describe Groovy::Model do
|
|
60
67
|
describe '.delete_all' do
|
61
68
|
|
62
69
|
before do
|
70
|
+
TestProduct.delete_all
|
63
71
|
@first = TestProduct.create!(name: 'A product', price: 100)
|
64
72
|
@second = TestProduct.create!(name: 'Another product', price: 200)
|
65
73
|
expect(TestProduct.count).to eq(2)
|
data/spec/query_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe Groovy::Query do
|
|
8
8
|
@p1 = TestProduct.create!(name: "Product 1", visible: true, price: 10, tag_list: 'one, number two & three')
|
9
9
|
@p2 = TestProduct.create!(name: "Product 2", visible: false, price: 20, tag_list: 'number two, three')
|
10
10
|
@p3 = TestProduct.create!(name: "Product 3: The Best", visible: true, price: 30, tag_list: nil)
|
11
|
-
@p4 = TestProduct.create!(name: "Product 4", visible: false, price: 40, tag_list: 'one, number two')
|
11
|
+
@p4 = TestProduct.create!(name: "Product 4", visible: false, price: 40, tag_list: 'one, number two / something')
|
12
12
|
@p5 = TestProduct.create!(name: "Product 5", visible: true, price: 50, tag_list: '')
|
13
13
|
end
|
14
14
|
|
@@ -124,6 +124,12 @@ describe Groovy::Query do
|
|
124
124
|
res = TestProduct.where(tag_list: /two & three/)
|
125
125
|
expect(res.map(&:id)).to eq([@p1.id])
|
126
126
|
end
|
127
|
+
|
128
|
+
it 'works with slashes' do
|
129
|
+
str = 'two / something'
|
130
|
+
res = TestProduct.where(tag_list: /#{str}/)
|
131
|
+
expect(res.map(&:id)).to eq([@p4.id])
|
132
|
+
end
|
127
133
|
end
|
128
134
|
|
129
135
|
describe 'starts with regex' do
|
@@ -253,6 +259,12 @@ describe Groovy::Query do
|
|
253
259
|
res = TestProduct.not(tag_list: /two & three/)
|
254
260
|
expect(res.map(&:id)).to eq([@p2.id, @p3.id, @p4.id, @p5.id])
|
255
261
|
end
|
262
|
+
|
263
|
+
it 'works with slashes' do
|
264
|
+
str = 'two / something'
|
265
|
+
res = TestProduct.not(tag_list: /#{str}/)
|
266
|
+
expect(res.map(&:id)).to eq([@p1.id, @p2.id, @p3.id, @p5.id])
|
267
|
+
end
|
256
268
|
end
|
257
269
|
|
258
270
|
describe 'starts with regex' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: groovy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomás Pollak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rroonga
|