groovy 0.4.1 → 0.4.6

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: b64b5151ebc9ad89b92fac064613d778e067449e11eb0c87cbf8ce40ca5e2630
4
- data.tar.gz: 3e95ca4bac3946bcf79805c174216be0cb2870c06e47369056054bfb4d61a637
3
+ metadata.gz: 2569117dfa3663d6c60ed950be2972c58116e7eaf6d21f5c6640cb91642fee4f
4
+ data.tar.gz: 33e1651825dd626dab979e9c504d5de1b7d8162363d42cbeca43873e370cad04
5
5
  SHA512:
6
- metadata.gz: 3320e1c8e3d31b9d48c2fbf409f2688b3be16734eebd631b9ee44f9509934968079b9ed1ab14a02422a559865e2619da1954640feed825bc17b8865ab96ac4d1
7
- data.tar.gz: 1d1ebb476d22fa504a2b1f7ef7585f298fb5fbc84dbdc22cb34997c1a8e57032622e0c12867aab730bf75f08293d7f093a49059efb68d11c150be5684e4c9c34
6
+ metadata.gz: 66b311fe79a3f9c8467ddbe9f863a2fd731b9c7dd9df2dbeb49fc1b8d62f4b7b506b2e0ca1f05c6ad7766ede89df0a26cb4a8eae8f96097ff613ab17cadde593
7
+ data.tar.gz: c1dbb4f84dfdb79d870a0e498acbc63d7e1268f5cfd1f0951fd16cc7d890b68a6d3679e903eb703b8de4a44b6765070594df0eb53b8cfbeb7ac9fb1499f5dd3f
@@ -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
@@ -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.time_column?(key)
410
+ if self.class.schema.time_columns.include?(key)
409
411
  fix_time_value(val)
410
412
  else
411
413
  val
@@ -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
- @sorting[:limit] = num
142
+ sorting[:limit] = num
136
143
  self
137
144
  end
138
145
 
139
146
  def offset(num)
140
- @sorting[:offset] = num
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) * PER_PAGE
147
- offset(offset).limit(PER_PAGE) # returns self
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 last(count = 1)
205
- if count > 1
206
- records[(size-count)..-1]
207
- else
208
- records[size-1]
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
- @sorting[:limit] = of
214
- @sorting[:offset] = from || 0
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
- @sorting[:offset] += of
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 parameters.any?
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}, #{sorting.inspect}"
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
@@ -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.1'.freeze
2
+ VERSION = '0.4.6'.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
 
@@ -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.1
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-07 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