groovy 0.4.0 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/example/basic.rb +3 -2
- data/example/relations.rb +1 -0
- data/lib/groovy.rb +8 -0
- data/lib/groovy/model.rb +21 -10
- data/lib/groovy/query.rb +22 -15
- 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 +27 -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: f2e7655a8fefa2f17295105acb6ba387f275eb158c0babed04675d566f736766
|
4
|
+
data.tar.gz: 5a9b94a767f58b033a11044d3b7992911a6cde0ad068955c76d85bdd2c0464b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c8813e0920559c6422f8129ca40e51c42a6db26efc65c9f2d9a1d47d8f5f6d5638f9677f970a38d36a180c93cf610b99b7adc372579eaff92ae4d8bd9aa41ae
|
7
|
+
data.tar.gz: d12ed329507b8a5c191308e198a5434f8226fd24bf4f25c8b2b8d64a832c05df39ff9946dddd53b7da5df11c2d14bc9ab97e4dcf84d67e60c9c2811d0c0122b6
|
data/example/basic.rb
CHANGED
@@ -9,6 +9,7 @@ class Product
|
|
9
9
|
schema do |t|
|
10
10
|
t.string :name
|
11
11
|
t.integer :price
|
12
|
+
t.boolean :published
|
12
13
|
t.timestamps
|
13
14
|
end
|
14
15
|
|
@@ -19,11 +20,11 @@ end
|
|
19
20
|
def populate
|
20
21
|
5_000.times do |i|
|
21
22
|
puts "Creating product #{i}" if i % 1000 == 0
|
22
|
-
Product.create!(name: "A product with index #{i}", price: 10000 + i % 10)
|
23
|
+
Product.create!(name: "A product with index #{i}", published: true, price: 10000 + i % 10)
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
populate if Product.count == 0
|
27
|
+
populate if Product.count == 0
|
27
28
|
|
28
29
|
# 50_000 products: 50M
|
29
30
|
# 100_000 products: 50M
|
data/example/relations.rb
CHANGED
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
@@ -208,7 +208,7 @@ module Groovy
|
|
208
208
|
end
|
209
209
|
end
|
210
210
|
|
211
|
-
[:find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
211
|
+
[:select, :find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
212
212
|
define_method scope_method do |*args, &block|
|
213
213
|
query.public_send(scope_method, *args, &block)
|
214
214
|
end
|
@@ -221,6 +221,12 @@ module Groovy
|
|
221
221
|
set_timestamp(attributes, :created_at)
|
222
222
|
set_timestamp(attributes, :updated_at)
|
223
223
|
|
224
|
+
# remove nil attributes for integer columns, otherwise
|
225
|
+
# we get a TypeError (no implicit conversion from nil to integer)
|
226
|
+
attributes.each do |k, v|
|
227
|
+
attributes.delete(k) if v.nil? # && schema.integer_columns.include?(k)
|
228
|
+
end
|
229
|
+
|
224
230
|
if table.support_key?
|
225
231
|
raise "Key required" if key.nil?
|
226
232
|
table.add(key, attributes)
|
@@ -297,7 +303,7 @@ module Groovy
|
|
297
303
|
else
|
298
304
|
attrs ||= {}
|
299
305
|
unless attrs.is_a?(Hash)
|
300
|
-
raise ArgumentError.new("Attributes should be a Hash")
|
306
|
+
raise ArgumentError.new("Attributes should be a Hash, not a #{attrs.class}")
|
301
307
|
end
|
302
308
|
|
303
309
|
# don't call set_attributes since we don't want to call
|
@@ -309,6 +315,12 @@ module Groovy
|
|
309
315
|
@changes = {}
|
310
316
|
end
|
311
317
|
|
318
|
+
# get reference to the actual record in the Groonga table,
|
319
|
+
# not the temporary one we get as part of a search result.
|
320
|
+
def load_record
|
321
|
+
self.class.table[id]
|
322
|
+
end
|
323
|
+
|
312
324
|
def inspect
|
313
325
|
"#<#{self.class.name} id:#{id.inspect} attributes:[#{self.class.attribute_names.join(', ')}]>"
|
314
326
|
end
|
@@ -394,22 +406,21 @@ module Groovy
|
|
394
406
|
end
|
395
407
|
|
396
408
|
def ==(other)
|
397
|
-
self.id == other.id
|
409
|
+
self.class == other.class && self.id == other.id
|
398
410
|
end
|
399
411
|
|
400
412
|
def <=>(other)
|
401
|
-
self.id <=> other.id
|
413
|
+
self.class == other.class && self.id <=> other.id
|
402
414
|
end
|
403
415
|
|
404
416
|
private
|
405
417
|
|
406
418
|
def get_record_attribute(key)
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
end
|
419
|
+
val = record[key]
|
420
|
+
if self.class.schema.time_columns.include?(key)
|
421
|
+
fix_time_value(val)
|
422
|
+
else
|
423
|
+
val
|
413
424
|
end
|
414
425
|
end
|
415
426
|
|
data/lib/groovy/query.rb
CHANGED
@@ -6,6 +6,7 @@ module Groovy
|
|
6
6
|
AND = '+'.freeze
|
7
7
|
NOT = '-'.freeze
|
8
8
|
PER_PAGE = 50.freeze
|
9
|
+
# ESCAPE_CHARS_REGEX = /([\(\)\/\\])/.freeze
|
9
10
|
VALID_QUERY_CHARS = 'a-zA-Z0-9_\.,&-'.freeze
|
10
11
|
REMOVE_INVALID_CHARS_REGEX = Regexp.new('[^\s' + VALID_QUERY_CHARS + ']').freeze
|
11
12
|
|
@@ -47,6 +48,11 @@ module Groovy
|
|
47
48
|
self
|
48
49
|
end
|
49
50
|
|
51
|
+
def select(&block)
|
52
|
+
@select_block = block
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
50
56
|
def find(id)
|
51
57
|
find_by(_id: id)
|
52
58
|
end
|
@@ -76,7 +82,7 @@ module Groovy
|
|
76
82
|
add_param(AND + [key, val.max].join(':<=')) if val.max # gte
|
77
83
|
|
78
84
|
elsif val.is_a?(Regexp)
|
79
|
-
str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
85
|
+
str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
80
86
|
param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
|
81
87
|
add_param(AND + [key, str.downcase].join(param)) # regex must be downcase
|
82
88
|
|
@@ -85,7 +91,7 @@ module Groovy
|
|
85
91
|
add_param(AND + str)
|
86
92
|
|
87
93
|
else
|
88
|
-
str = val.nil? || val
|
94
|
+
str = val.nil? || val === false || val.to_s.strip == '' ? "\"\"" : escape_val(val)
|
89
95
|
add_param(AND + [key, str].join(':'))
|
90
96
|
end
|
91
97
|
end
|
@@ -110,7 +116,7 @@ module Groovy
|
|
110
116
|
add_param(AND + [key, val.max].join(':>=')) if val.max # lte, nil if range.max is -1
|
111
117
|
|
112
118
|
elsif val.is_a?(Regexp)
|
113
|
-
str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
119
|
+
str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
|
114
120
|
param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
|
115
121
|
add_param(NOT + [key, str.downcase].join(param)) # regex must be downcase
|
116
122
|
|
@@ -119,7 +125,7 @@ module Groovy
|
|
119
125
|
add_param(AND + str)
|
120
126
|
|
121
127
|
else
|
122
|
-
str = val.nil? || val
|
128
|
+
str = val.nil? || val === false || val.to_s.strip == '' ? "\"\"" : escape_val(val)
|
123
129
|
add_param(AND + [key, str].join(':!')) # not
|
124
130
|
end
|
125
131
|
end
|
@@ -141,10 +147,10 @@ module Groovy
|
|
141
147
|
self
|
142
148
|
end
|
143
149
|
|
144
|
-
def paginate(page = 1)
|
150
|
+
def paginate(page = 1, per_page: PER_PAGE)
|
145
151
|
page = 1 if page.to_i < 1
|
146
|
-
offset = ((page.to_i)-1) *
|
147
|
-
offset(offset).limit(
|
152
|
+
offset = ((page.to_i)-1) * per_page
|
153
|
+
offset(offset).limit(per_page) # returns self
|
148
154
|
end
|
149
155
|
|
150
156
|
# sort_by(title: :asc)
|
@@ -229,15 +235,12 @@ module Groovy
|
|
229
235
|
end
|
230
236
|
|
231
237
|
private
|
232
|
-
attr_reader :model, :table, :options
|
238
|
+
attr_reader :model, :table, :options, :select_block
|
233
239
|
|
234
240
|
def add_param(param)
|
235
|
-
if
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
# if param matches blank/nil, put at the end of the stack
|
240
|
-
param[/:\!?\\/] ? parameters.push(param) : parameters.unshift(param)
|
241
|
+
raise "Select block already given!" if select_block
|
242
|
+
raise "Duplicate param: #{param}" if parameters.include?(param)
|
243
|
+
parameters.push(param)
|
241
244
|
end
|
242
245
|
|
243
246
|
def results
|
@@ -248,7 +251,10 @@ module Groovy
|
|
248
251
|
end
|
249
252
|
|
250
253
|
def execute
|
251
|
-
set = if
|
254
|
+
set = if select_block
|
255
|
+
debug "Finding records with select block"
|
256
|
+
table.select { |record| select_block.call(record) }
|
257
|
+
elsif parameters.any?
|
252
258
|
query = prepare_query
|
253
259
|
debug "Finding records with query: #{query}"
|
254
260
|
table.select(query, options)
|
@@ -291,6 +297,7 @@ module Groovy
|
|
291
297
|
query = parameters.join(' ').split(/ or /i).map do |part|
|
292
298
|
part.gsub(' ', ' ') # replace double with single spaces
|
293
299
|
.gsub(space_regex, '\ \1') # escape spaces before word letters
|
300
|
+
.gsub('_slash_', '\/').gsub('_openp_', '\(').gsub('_closep_', '\)')
|
294
301
|
.gsub(/(\d\d):(\d\d):(\d\d)/, '\1\:\2\:\3') # escape hh:mm:ss in timestamps
|
295
302
|
end.join(' OR ').sub(/^-/, '_id:>0 -') #.gsub(' OR -', ' -')
|
296
303
|
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
|
|
@@ -40,6 +40,13 @@ describe Groovy::Query do
|
|
40
40
|
res = TestProduct.where(tag_list: nil)
|
41
41
|
expect(res.map(&:id)).to eq([@p3.id, @p5.id])
|
42
42
|
end
|
43
|
+
it 'works with other nil values too' do
|
44
|
+
res = TestProduct.where(visible: nil).where(tag_list: nil)
|
45
|
+
expect(res.map(&:id)).to eq([])
|
46
|
+
|
47
|
+
res = TestProduct.where(tag_list: nil).where(name: nil)
|
48
|
+
expect(res.map(&:id)).to eq([])
|
49
|
+
end
|
43
50
|
it 'works with other args too' do
|
44
51
|
res = TestProduct.where(name: 'Product 5').where(tag_list: nil)
|
45
52
|
expect(res.map(&:id)).to eq([@p5.id])
|
@@ -117,6 +124,12 @@ describe Groovy::Query do
|
|
117
124
|
res = TestProduct.where(tag_list: /two & three/)
|
118
125
|
expect(res.map(&:id)).to eq([@p1.id])
|
119
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
|
120
133
|
end
|
121
134
|
|
122
135
|
describe 'starts with regex' do
|
@@ -187,6 +200,13 @@ describe Groovy::Query do
|
|
187
200
|
res = TestProduct.where.not(tag_list: nil)
|
188
201
|
expect(res.map(&:id)).to eq([@p1.id, @p2.id, @p4.id])
|
189
202
|
end
|
203
|
+
it 'works with other nil values too' do
|
204
|
+
res = TestProduct.where.not(visible: nil).where(tag_list: nil)
|
205
|
+
expect(res.map(&:id)).to eq([@p3.id, @p5.id])
|
206
|
+
|
207
|
+
res = TestProduct.where.not(tag_list: nil).where(name: nil)
|
208
|
+
expect(res.map(&:id)).to eq([])
|
209
|
+
end
|
190
210
|
it 'works with other args too' do
|
191
211
|
res = TestProduct.where.not(name: 'Product 2').where.not(tag_list: nil)
|
192
212
|
expect(res.map(&:id)).to eq([@p1.id, @p4.id])
|
@@ -239,6 +259,12 @@ describe Groovy::Query do
|
|
239
259
|
res = TestProduct.not(tag_list: /two & three/)
|
240
260
|
expect(res.map(&:id)).to eq([@p2.id, @p3.id, @p4.id, @p5.id])
|
241
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
|
242
268
|
end
|
243
269
|
|
244
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.5
|
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-
|
11
|
+
date: 2020-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rroonga
|