groovy 0.4.2 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2e735dd501cd86a3ee2ab21b111bf7aa4ab3d76d9d14acf8be86b97e34335b1
4
- data.tar.gz: 97b3b88d1f297ab6d74b9818858272c8fa711a27a3c0d64964508988a3304b85
3
+ metadata.gz: 43812d3bd8ef9579ceed22b3d19e78378ceb3a924bfc8d6c0983e49199c26c88
4
+ data.tar.gz: 5960ca73a253d62f31301995c8f643bcaa084a99140b5f59eee0161c816d20e2
5
5
  SHA512:
6
- metadata.gz: deb85e6549f328555d795e542e282c6e3a50c574fdac769530648c69292f02ed469bc293304db863b7c9ac28a5bb653bd85eb79a7652f6390b3f5309fc492552
7
- data.tar.gz: 7546011290684eca615cf4823df5886908fe823c9bee086a80504a01a7d3aa846a0fd34dc05efc023a061078ac3645d352618c64a03b614989cb51bc3ca4c5c9
6
+ metadata.gz: 22cc09a73d9804f5d5e1cb432d3948ff24ef9a28dbd84b5a403b3709477d013089617ebf19b23f4d3450c3c7dc8e15c01ab527698c5efa5821c9d351056777ad
7
+ data.tar.gz: 809b9fa8e3158df7afeb6e80967d7156772094b21b69f6fe7d9f5e6db03626fe8a9b2ee123f89c7bf791ef6e195415524b1e967291d836becdc6173fbd0bfbfc
@@ -28,4 +28,18 @@ populate if Product.count == 0
28
28
 
29
29
  # 50_000 products: 50M
30
30
  # 100_000 products: 50M
31
- # 500_000 products: 62M
31
+ # 500_000 products: 62M
32
+
33
+ module MemInfo
34
+ KERNEL_PAGE_SIZE = `getconf PAGESIZE`.chomp.to_i rescue 4096
35
+ STATM_PATH = "/proc/#{Process.pid}/statm"
36
+ STATM_FOUND = File.exist?(STATM_PATH)
37
+ def self.rss
38
+ STATM_FOUND ? (File.read(STATM_PATH).split(' ')[1].to_i * KERNEL_PAGE_SIZE) / 1024 : 0
39
+ end
40
+ end
41
+
42
+ puts MemInfo.rss
43
+ prices = Product.all.map { |x| x.price }
44
+ puts prices.inspect
45
+ puts MemInfo.rss
@@ -150,7 +150,6 @@ module Groovy
150
150
  # Groonga["#{table_name}.#{name}"] # .search, .similar_search, etc
151
151
  # end
152
152
 
153
-
154
153
  def index_search(column, query, options = {}, &block)
155
154
  results = table.select { |rec| rec[column].match(query) }
156
155
  render_results(results, &block)
@@ -187,16 +186,6 @@ module Groovy
187
186
  query
188
187
  end
189
188
 
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
189
  def query
201
190
  query_class.new(self, table)
202
191
  end
@@ -208,7 +197,7 @@ module Groovy
208
197
  end
209
198
  end
210
199
 
211
- [:select, :find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
200
+ [:first, :last, :select, :find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
212
201
  define_method scope_method do |*args, &block|
213
202
  query.public_send(scope_method, *args, &block)
214
203
  end
@@ -221,6 +210,12 @@ module Groovy
221
210
  set_timestamp(attributes, :created_at)
222
211
  set_timestamp(attributes, :updated_at)
223
212
 
213
+ # remove nil attributes for integer columns, otherwise
214
+ # we get a TypeError (no implicit conversion from nil to integer)
215
+ attributes.each do |k, v|
216
+ attributes.delete(k) if v.nil? # && schema.integer_columns.include?(k)
217
+ end
218
+
224
219
  if table.support_key?
225
220
  raise "Key required" if key.nil?
226
221
  table.add(key, attributes)
@@ -287,17 +282,17 @@ module Groovy
287
282
  end
288
283
  end
289
284
 
290
- attr_reader :id, :attributes, :record, :changes
285
+ attr_reader :id, :record, :changes
291
286
 
292
287
  def initialize(attrs = nil, record = nil, key = nil)
293
288
  @attributes, @vectors, @_key = {}, {}, key # key is used on creation only
294
289
 
295
290
  if set_record(record)
296
- set_attributes_from_record(record)
291
+ # load_attributes_from_record(record)
297
292
  else
298
293
  attrs ||= {}
299
294
  unless attrs.is_a?(Hash)
300
- raise ArgumentError.new("Attributes should be a Hash")
295
+ raise ArgumentError.new("Attributes should be a Hash, not a #{attrs.class}")
301
296
  end
302
297
 
303
298
  # don't call set_attributes since we don't want to call
@@ -311,8 +306,13 @@ module Groovy
311
306
 
312
307
  # get reference to the actual record in the Groonga table,
313
308
  # not the temporary one we get as part of a search result.
314
- def load_record
315
- self.class.table[id]
309
+ # def load_record
310
+ # self.class.table[id]
311
+ # end
312
+
313
+ def attributes
314
+ load_attributes_from_record # populate missing
315
+ @attributes
316
316
  end
317
317
 
318
318
  def inspect
@@ -325,7 +325,9 @@ module Groovy
325
325
  end
326
326
 
327
327
  def [](key)
328
- attributes[key.to_sym]
328
+ k = key.to_sym
329
+ @attributes[k] = get_record_attribute(k) unless @attributes.key?(k)
330
+ @attributes[k]
329
331
  end
330
332
 
331
333
  def []=(key, val)
@@ -386,11 +388,15 @@ module Groovy
386
388
  end
387
389
 
388
390
  def reload
389
- raise RecordNotPersisted if id.nil?
390
- ensure_persisted!
391
- rec = self.class.table[id] # _key
392
- # set_record(rec)
393
- set_attributes_from_record(rec)
391
+ unless new_record?
392
+ # raise RecordNotPersisted if id.nil?
393
+ # ensure_persisted!
394
+ rec = self.class.table[id] # _key
395
+ set_record(rec)
396
+ # load_attributes_from_record
397
+ end
398
+
399
+ @attributes = {}
394
400
  @changes = {}
395
401
  self
396
402
  end
@@ -400,18 +406,19 @@ module Groovy
400
406
  end
401
407
 
402
408
  def ==(other)
403
- self.id == other.id
409
+ self.class == other.class && self.id == other.id
404
410
  end
405
411
 
406
412
  def <=>(other)
407
- self.id <=> other.id
413
+ self.class == other.class && self.id <=> other.id
408
414
  end
409
415
 
410
416
  private
411
417
 
412
418
  def get_record_attribute(key)
419
+ return if record.nil?
413
420
  val = record[key]
414
- if self.class.schema.time_column?(key)
421
+ if self.class.schema.time_columns.include?(key)
415
422
  fix_time_value(val)
416
423
  else
417
424
  val
@@ -427,15 +434,15 @@ module Groovy
427
434
  # record.respond_to?(:_key) ? record._key : id
428
435
  # end
429
436
 
430
- def set_attributes_from_record(rec)
437
+ def load_attributes_from_record
431
438
  self.class.attribute_names.each do |col|
432
- public_send("#{col}=", get_record_attribute(col))
439
+ public_send("#{col}=", get_record_attribute(col)) unless @attributes.key?(col)
433
440
  end
434
441
  end
435
442
 
436
443
  def set_attribute(key, val)
437
- changes[key.to_sym] = [self[key], val] if changes # nil when initializing
438
- attributes[key.to_sym] = val
444
+ changes[key.to_sym] = [self[key], val] if changes # nil before initializing
445
+ @attributes[key.to_sym] = val
439
446
  end
440
447
 
441
448
  def get_ref(name)
@@ -460,7 +467,7 @@ module Groovy
460
467
 
461
468
  def create
462
469
  fire_callbacks(:before_create)
463
- set_record(self.class.insert(attributes, @_key))
470
+ set_record(self.class.insert(@attributes, @_key))
464
471
  fire_callbacks(:after_create)
465
472
  self
466
473
  end
@@ -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
 
@@ -81,7 +83,7 @@ module Groovy
81
83
  add_param(AND + [key, val.max].join(':<=')) if val.max # gte
82
84
 
83
85
  elsif val.is_a?(Regexp)
84
- str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
86
+ str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
85
87
  param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
86
88
  add_param(AND + [key, str.downcase].join(param)) # regex must be downcase
87
89
 
@@ -115,7 +117,7 @@ module Groovy
115
117
  add_param(AND + [key, val.max].join(':>=')) if val.max # lte, nil if range.max is -1
116
118
 
117
119
  elsif val.is_a?(Regexp)
118
- str = val.source.gsub(REMOVE_INVALID_CHARS_REGEX, '')
120
+ str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
119
121
  param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
120
122
  add_param(NOT + [key, str.downcase].join(param)) # regex must be downcase
121
123
 
@@ -137,12 +139,12 @@ module Groovy
137
139
  end
138
140
 
139
141
  def limit(num)
140
- @sorting[:limit] = num
142
+ sorting[:limit] = num
141
143
  self
142
144
  end
143
145
 
144
146
  def offset(num)
145
- @sorting[:offset] = num
147
+ sorting[:offset] = num
146
148
  self
147
149
  end
148
150
 
@@ -154,8 +156,8 @@ module Groovy
154
156
 
155
157
  # sort_by(title: :asc)
156
158
  def sort_by(hash)
157
- if hash.is_a?(String) || hash.is_a?(Symbol) # e.g. 'title.desc' or :title (asc by default)
158
- 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|\./)
159
161
  hash = {}
160
162
  hash[param] = dir || 'asc'
161
163
  end
@@ -190,11 +192,16 @@ module Groovy
190
192
  end
191
193
 
192
194
  def [](index)
193
- records[index]
195
+ if r = results[index]
196
+ model.new_from_record(r)
197
+ end
194
198
  end
195
199
 
196
200
  def each(&block)
197
- records.each { |r| block.call(r) }
201
+ # records.each { |r| block.call(r) }
202
+ results.each_with_index do |r, index|
203
+ yield model.new_from_record(r)
204
+ end
198
205
  end
199
206
 
200
207
  def update_all(attrs)
@@ -206,35 +213,50 @@ module Groovy
206
213
  @total_entries
207
214
  end
208
215
 
209
- def last(count = 1)
210
- if count > 1
211
- records[(size-count)..-1]
212
- else
213
- records[size-1]
216
+ def first(num = 1)
217
+ limit(num)
218
+ num == 1 ? records.first : records
219
+ end
220
+
221
+ def last(num = 1)
222
+ if sorting[:by]
223
+ get_last(num)
224
+ else # no sorting, so
225
+ sort_by(_id: :desc).limit(num)
226
+ # if last(2) or more, then re-sort by ascending ID
227
+ num == 1 ? records.first : records.sort { |a,b| a.id <=> b.id }
214
228
  end
215
229
  end
216
230
 
217
231
  def in_batches(of: 1000, from: nil, &block)
218
- @sorting[:limit] = of
219
- @sorting[:offset] = from || 0
232
+ sorting[:limit] = of
233
+ sorting[:offset] = from || 0
220
234
 
221
235
  while results.any?
222
236
  yield to_a
223
237
  break if results.size < of
224
238
 
225
- @sorting[:offset] += of
239
+ sorting[:offset] += of
226
240
  @records = @results = nil # reset
227
241
  end
228
242
  end
229
243
 
244
+ private
245
+ attr_reader :model, :table, :options, :select_block
246
+
230
247
  def records
231
248
  @records ||= results.map do |r|
232
249
  model.new_from_record(r)
233
250
  end
234
251
  end
235
252
 
236
- private
237
- attr_reader :model, :table, :options, :select_block
253
+ def get_last(count = 1)
254
+ if count > 1
255
+ records[(size-count)..-1]
256
+ else
257
+ records[size-1]
258
+ end
259
+ end
238
260
 
239
261
  def add_param(param)
240
262
  raise "Select block already given!" if select_block
@@ -264,7 +286,7 @@ module Groovy
264
286
 
265
287
  @total_entries = set.size
266
288
 
267
- debug "Sorting with #{sort_key_and_order}, #{sorting.inspect}"
289
+ debug "Sorting with #{sort_key_and_order} (options: #{sorting.inspect})"
268
290
  set = set.sort(sort_key_and_order, {
269
291
  limit: sorting[:limit],
270
292
  offset: sorting[:offset], # [sorting[:offset], @total_entries].min
@@ -296,6 +318,7 @@ module Groovy
296
318
  query = parameters.join(' ').split(/ or /i).map do |part|
297
319
  part.gsub(' ', ' ') # replace double with single spaces
298
320
  .gsub(space_regex, '\ \1') # escape spaces before word letters
321
+ .gsub('_slash_', '\/').gsub('_openp_', '\(').gsub('_closep_', '\)')
299
322
  .gsub(/(\d\d):(\d\d):(\d\d)/, '\1\:\2\:\3') # escape hh:mm:ss in timestamps
300
323
  end.join(' OR ').sub(/^-/, '_id:>0 -') #.gsub(' OR -', ' -')
301
324
  end
@@ -26,6 +26,7 @@ module Groovy
26
26
  def initialize(context, table_name, opts = {})
27
27
  @context, @table_name, @opts = context, table_name, opts || {}
28
28
  @spec, @index_columns = {}, []
29
+ @cache = {}
29
30
  end
30
31
 
31
32
  def table
@@ -33,35 +34,43 @@ module Groovy
33
34
  end
34
35
 
35
36
  def search_table
36
- @search_table ||= context[SEARCH_TABLE_NAME]
37
+ @cache[:search_table] ||= context[SEARCH_TABLE_NAME]
37
38
  end
38
39
 
39
40
  def column_names
40
- get_names table.columns
41
+ @cache[:column_names] ||= get_names(table.columns)
41
42
  end
42
43
 
43
44
  def singular_references
44
- # @singular_references ||=
45
- get_names(table.columns.select(&:reference_column?).reject(&:vector?))
45
+ @cache[:singular_references] ||= get_names(table.columns.select(&:reference_column?).reject(&:vector?))
46
46
  end
47
47
 
48
48
  def plural_references
49
- # @plural_references ||=
50
- get_names(table.columns.select(&:vector?))
49
+ @cache[:plural_references] ||= get_names(table.columns.select(&:vector?))
51
50
  end
52
51
 
53
52
  def attribute_columns
54
- # @attribute_columns ||=
55
- get_names(table.columns.select { |c| c.column? && !c.reference_column? && !c.vector? })
53
+ @cache[:attribute_columns] ||= get_names(table.columns.select { |c| c.column? && !c.reference_column? && !c.vector? })
56
54
  end
57
55
 
58
56
  def time_columns
59
- # @time_columns ||=
60
- get_names(table.columns.select { |c| c.column? && c.range.name == 'Time' })
57
+ @cache[:time_columns] ||= columns_by_type('Time')
61
58
  end
62
59
 
63
- def time_column?(name)
64
- time_columns.include?(name)
60
+ def integer_columns
61
+ @cache[:integer_columns] ||= columns_by_type('Int32')
62
+ end
63
+
64
+ def boolean_columns
65
+ @cache[:boolean_columns] ||= columns_by_type('Bool')
66
+ end
67
+
68
+ def columns_by_type(type)
69
+ get_names(table.columns.select { |c| c.column? && c.range.name == type })
70
+ end
71
+
72
+ def reload
73
+ @cache = {}
65
74
  end
66
75
 
67
76
  def rebuild!
@@ -105,6 +114,8 @@ module Groovy
105
114
  @index_columns.each do |col|
106
115
  add_index_on(col)
107
116
  end
117
+
118
+ reload
108
119
  self
109
120
  end
110
121
 
@@ -1,3 +1,3 @@
1
1
  module Groovy
2
- VERSION = '0.4.2'.freeze
2
+ VERSION = '0.4.7'.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)
@@ -78,6 +86,28 @@ describe Groovy::Model do
78
86
  end
79
87
 
80
88
  describe '#[]' do
89
+ it 'reads value from record' do
90
+ prod = TestProduct.create!(name: 'A product', price: 100)
91
+ expect(prod.name).to eq('A product')
92
+ expect(prod['name']).to eq('A product')
93
+ expect(prod[:name]).to eq('A product')
94
+
95
+ prod = TestProduct.find(prod.id)
96
+ expect(prod.name).to eq('A product')
97
+ expect(prod['name']).to eq('A product')
98
+ expect(prod[:name]).to eq('A product')
99
+
100
+ prod = TestProduct.new
101
+ expect(prod.name).to eq(nil)
102
+ prod.name = 'Another product'
103
+ expect(prod.name).to eq('Another product')
104
+ prod.reload
105
+ expect(prod.name).to eq(nil)
106
+ prod.name = 'Another product'
107
+ prod.save
108
+ prod.reload
109
+ expect(prod.name).to eq('Another product')
110
+ end
81
111
  end
82
112
 
83
113
  describe '#[]=' do
@@ -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.2
4
+ version: 0.4.7
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-13 00:00:00.000000000 Z
11
+ date: 2020-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rroonga