groovy 0.4.0 → 0.4.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1019bd31fda4785bec67322dfb8aeddfab4d0fa12143b23a8dd75f4a3c84515e
4
- data.tar.gz: 44ab6a5fd87d136bc3b6915ab323fac95b6a8012f5cdc4ea2fb6498d5236cb3a
3
+ metadata.gz: f2e7655a8fefa2f17295105acb6ba387f275eb158c0babed04675d566f736766
4
+ data.tar.gz: 5a9b94a767f58b033a11044d3b7992911a6cde0ad068955c76d85bdd2c0464b9
5
5
  SHA512:
6
- metadata.gz: 2ae8f4bcfb8d6166fa4ad8814521c498a44f3e512de23c4cb986d7ce7cd4ef98722a889c3a8e69e1f55bdfc9651f19a9b03e9d31bcb2f87552c73c194fc8f099
7
- data.tar.gz: 4a29d7007af10d67f4265ef9e431fa347ebc979745c87037fbf8957e2441fc012904cf5385d8aad8d1da6708e91ffae6b407acecf6e3c8e19f2f0bc787d90639
6
+ metadata.gz: 2c8813e0920559c6422f8129ca40e51c42a6db26efc65c9f2d9a1d47d8f5f6d5638f9677f970a38d36a180c93cf610b99b7adc372579eaff92ae4d8bd9aa41ae
7
+ data.tar.gz: d12ed329507b8a5c191308e198a5434f8226fd24bf4f25c8b2b8d64a832c05df39ff9946dddd53b7da5df11c2d14bc9ab97e4dcf84d67e60c9c2811d0c0122b6
@@ -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
@@ -34,6 +34,7 @@ class Place
34
34
  t.reference :categories, "Categories", type: :vector
35
35
  t.string :name
36
36
  t.string :description, index: true
37
+ t.boolean :visible
37
38
  t.timestamps
38
39
  end
39
40
 
@@ -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
@@ -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
- if val = record[key]
408
- if self.class.schema.time_column?(key)
409
- fix_time_value(val)
410
- else
411
- val
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
 
@@ -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 == false || val.to_s.strip == '' ? '\\' : escape_val(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 == false || val.to_s.strip == '' ? '\\' : escape_val(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) * PER_PAGE
147
- offset(offset).limit(PER_PAGE) # returns self
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 parameters.include?(param)
236
- raise "Duplicate param: #{param}"
237
- end
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 parameters.any?
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
@@ -56,14 +56,25 @@ module Groovy
56
56
  end
57
57
 
58
58
  def time_columns
59
- # @time_columns ||=
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 time_column?(name)
64
- time_columns.include?(name)
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
@@ -1,3 +1,3 @@
1
1
  module Groovy
2
- VERSION = '0.4.0'.freeze
2
+ VERSION = '0.4.5'.freeze
3
3
  end
@@ -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)
@@ -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.0
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-09-09 00:00:00.000000000 Z
11
+ date: 2020-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rroonga