groovy 0.1.2 → 0.1.3

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: 21ec75f430aa5b3ea423b52de62b4d578b4eb0b1e6722e9201c012cd444c616b
4
- data.tar.gz: ec46772137b69e14dfe364d383fa9042ec8edd798bd22ed5c2dd4a47f895f1a4
3
+ metadata.gz: 4a5a7078b71e609451ea64d58ee47e7573f7570563118c1b1fdf62078617ce8d
4
+ data.tar.gz: 7444d38fc53884cb3540c0ab73efd91f296fcfa96828890cc29509831e6ae656
5
5
  SHA512:
6
- metadata.gz: 044e5ba04d8097a354be1a6feb07c2ddd06c6e28355a07b41586936a4e572e028f1aefd35bb587dff46f2d1bbad513332d915e7aec01c00df26b09f6a3ffb357
7
- data.tar.gz: 30ae3849bb6d769beb08db3ea8c676a0270a1037eb854c5ba7f4df2c3467906ad06055c7409ef8f2cf5284712f668c7153055b77470d0fa902e2d808591eedc4
6
+ metadata.gz: c20a741e4074aae0e28164ed307080229257d9c1fb6e00bd9fbe50f7b558b0189c1172cadc0ecd9b56484724b442a9bdd5bd59e67fbf748bdee9f4c39642fbd0
7
+ data.tar.gz: e6481be3ce2ae9fc87071bf1865e1672e7da9b3af0eee39a69dc718be6c4ef39b5dbb942cdd466fe26ccc2035fa5aba70037c12a2f373b44de42d88e3d7cc4b6
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- groovy (0.1.1)
4
+ groovy (0.1.2)
5
5
  rroonga (~> 7.1)
6
6
 
7
7
  GEM
@@ -16,7 +16,7 @@ end
16
16
  def populate
17
17
  5_000.times do |i|
18
18
  puts "Creating product #{i}" if i % 1000 == 0
19
- Product.create!(name: "A product with index #{i}", price: 10000 + i)
19
+ Product.create!(name: "A product with index #{i}", price: 10000 + i % 10)
20
20
  end
21
21
  end
22
22
 
@@ -2,7 +2,7 @@ require 'bundler/setup'
2
2
  require 'groovy'
3
3
 
4
4
  Groovy.open('./db/2019', :current)
5
- Groovy.open('./db/2020', :next)
5
+ # Groovy.open('./db/2020', :next)
6
6
 
7
7
  module Groovy::Model::ClassMethods
8
8
  def context_name
@@ -44,4 +44,13 @@ class Location
44
44
  t.string :coords
45
45
  t.timestamps
46
46
  end
47
- end
47
+ end
48
+
49
+ def insert_places(count = 1000)
50
+ puts "Inserting #{count} places!"
51
+ count.times do |i|
52
+ Place.create!(name: "Place #{i}", description: "A nice place")
53
+ end
54
+ end
55
+
56
+ insert_places if Place.count == 0
@@ -9,6 +9,10 @@ class Hash
9
9
  end unless {}.respond_to?(:symbolize_keys)
10
10
 
11
11
  module Groovy
12
+
13
+ class Error < StandardError; end
14
+ class RecordNotPersisted < Error; end
15
+
12
16
  module Model
13
17
 
14
18
  def self.initialize_from_record(obj)
@@ -61,7 +65,8 @@ module Groovy
61
65
 
62
66
  def scope(name, obj)
63
67
  define_singleton_method(name) do |*args|
64
- obj.respond_to?(:call) ? obj.call(*args) : obj
68
+ res = obj.respond_to?(:call) ? obj.call(*args) : obj
69
+ self
65
70
  end
66
71
  end
67
72
 
@@ -87,7 +92,8 @@ module Groovy
87
92
  end
88
93
 
89
94
  def delete_all
90
- schema.rebuild!
95
+ all.each { |child| child.delete }.count
96
+ # schema.rebuild!
91
97
  end
92
98
 
93
99
  # def column(name)
@@ -103,18 +109,34 @@ module Groovy
103
109
  # table.select("#{col}:#{q}", operator: Groonga::Operation::SIMILAR)
104
110
  end
105
111
 
112
+ def unique_values_for(column, limit: -1)
113
+ cols = [column]
114
+ opts = { drill_down: cols, drill_down_limit: limit, drilldown_sortby: '_key' }
115
+ resp = table.context.select(table_name, opts) # drill_down_output_columns: cols
116
+ arr = resp.body[1] # or resp.drilldowns
117
+ arr.slice(2..-1) # .flatten
118
+ end
119
+
120
+ def clear_query
121
+ @query = nil
122
+ end
123
+
124
+ def query
125
+ @query ||= Query.new(self, table)
126
+ end
127
+
106
128
  def all
107
- Query.new(self, table)
129
+ query
108
130
  end
109
131
 
110
- def first(count = 1)
111
- arr = limit(count)
112
- count == 1 && arr.first || arr
132
+ def first(num = 1)
133
+ arr = limit(num)
134
+ num == 1 ? arr.first : arr
113
135
  end
114
136
 
115
- def last(count = 1)
116
- arr = limit(count).to_a.reverse
117
- count == 1 && arr.first || arr
137
+ def last(num = 1)
138
+ arr = all.sort_by(_id: :desc).limit(num)
139
+ num == 1 ? arr.first : arr
118
140
  end
119
141
 
120
142
  def find_by(params)
@@ -127,9 +149,7 @@ module Groovy
127
149
 
128
150
  [:search, :where, :not, :sort_by, :limit, :offset].each do |scope_method|
129
151
  define_method scope_method do |*args|
130
- Query.new(self, table).tap do |q|
131
- q.public_send(scope_method, *args)
132
- end
152
+ query.public_send(scope_method, *args)
133
153
  end
134
154
  end
135
155
 
@@ -191,28 +211,31 @@ module Groovy
191
211
 
192
212
  attr_reader :id, :attributes, :refs, :record, :changes
193
213
 
194
- def initialize(attributes = nil, record = nil)
214
+ def initialize(attrs = nil, record = nil)
195
215
  @attributes, @refs, @vectors = {}, {}, {}
196
-
197
- if @record = record
198
- @id = attributes.delete('_id')
216
+
217
+ if set_record(record)
199
218
  # TODO: lazy load this
200
219
  # self.class.schema.singular_references.each do |col|
201
220
  # set_ref(col, record.public_send(col))
202
221
  # end
203
222
  end
204
223
 
205
- set_attributes(attributes)
224
+ attrs ||= {}
225
+ unless attrs.is_a?(Hash)
226
+ raise ArgumentError.new("Attributes should be a hash")
227
+ end
228
+
229
+ # don't call set_attributes since we don't want to call
230
+ # setters, that might be overriden with custom logic.
231
+ # attrs.each { |k,v| self[k] = v }
232
+ set_attributes(attrs)
206
233
  @changes = {}
207
234
  end
208
235
 
209
236
  def new_record?
210
237
  id.nil?
211
- end
212
-
213
- def key
214
- return unless record
215
- record.respond_to?(:_key) ? record._key : record.id
238
+ # _key.nil?
216
239
  end
217
240
 
218
241
  def [](key)
@@ -239,6 +262,7 @@ module Groovy
239
262
  end
240
263
 
241
264
  def set_attributes(obj = {})
265
+ obj.delete('_id') # just in case
242
266
  # we call the method instead of []= to allow overriding setters
243
267
  obj.each { |k,v| public_send("#{k}=", v) }
244
268
  end
@@ -250,7 +274,7 @@ module Groovy
250
274
 
251
275
  def save(options = {})
252
276
  return false if respond_to?(:invalid) and invalid?
253
- @record ? update : create
277
+ new_record? ? create : update
254
278
  end
255
279
 
256
280
  def save!(options = {})
@@ -258,15 +282,21 @@ module Groovy
258
282
  end
259
283
 
260
284
  def delete
261
- record.delete
285
+ record.delete # doesn't work if record.id doesn't match _key
286
+ # self.class.table.delete(_key)
287
+ set_record(nil)
262
288
  self
289
+ rescue Groonga::InvalidArgument => e
290
+ puts "Error: #{e.inspect}"
291
+ raise RecordNotPersisted
263
292
  end
264
293
 
265
294
  def reload
266
- raise "Not persisted" if key.nil?
267
- record = self.class.table[key]
268
- attributes = record.attributes.symbolize_keys
269
- changes = {}
295
+ ensure_persisted!
296
+ record = self.class.table[id] # _key
297
+ # TODO: fix duplication
298
+ set_attributes(record.attributes)
299
+ @changes = {}
270
300
  self
271
301
  end
272
302
 
@@ -276,6 +306,11 @@ module Groovy
276
306
 
277
307
  private
278
308
 
309
+ # def _key
310
+ # return unless record
311
+ # record.respond_to?(:_key) ? record._key : id
312
+ # end
313
+
279
314
  def set_attribute(key, val)
280
315
  changes[key.to_sym] = [self[key], val] if changes # nil when initializing
281
316
  attributes[key.to_sym] = val
@@ -294,13 +329,18 @@ module Groovy
294
329
  set_attribute(name, obj.nil? ? nil : obj.key)
295
330
  end
296
331
 
332
+ def set_record(obj)
333
+ @id = obj ? obj._id : nil
334
+ @record = obj # return value
335
+ end
336
+
297
337
  def create
298
- @record = self.class.insert(attributes)
338
+ set_record(self.class.insert(attributes))
299
339
  self
300
340
  end
301
341
 
302
342
  def update
303
- raise "Not persisted" unless key
343
+ ensure_persisted!
304
344
  changes.each do |key, values|
305
345
  # puts "Updating #{key} from #{values[0]} to #{values[1]}"
306
346
  record[key] = values.last
@@ -310,5 +350,9 @@ module Groovy
310
350
  self
311
351
  end
312
352
 
353
+ def ensure_persisted!
354
+ raise "Not persisted" if new_record?
355
+ end
356
+
313
357
  end
314
358
  end
@@ -1,6 +1,7 @@
1
1
  module Groovy
2
2
 
3
3
  class Query
4
+
4
5
  include Enumerable
5
6
  AND = '+'.freeze
6
7
  NOT = '-'.freeze
@@ -31,6 +32,11 @@ module Groovy
31
32
  end
32
33
  end
33
34
 
35
+ def inspect
36
+ clear_query
37
+ super
38
+ end
39
+
34
40
  # http://groonga.org/docs/reference/grn_expr/query_syntax.html
35
41
  # TODO: support match_columns (search value in two or more columns)
36
42
  def where(conditions = nil)
@@ -46,6 +52,9 @@ module Groovy
46
52
  str = val.source.gsub(/[^a-z]/, '')
47
53
  param = val.source[0] == '^' ? ':^' : ':~' # starts with or regexp
48
54
  parameters.push(AND + [key, str].join(param))
55
+ elsif val.is_a?(Array) # { foo: [1,2,3] }
56
+ str = "#{key}:#{val.join(" OR #{key}:")}"
57
+ parameters.push(AND + str)
49
58
  else
50
59
  parameters.push(AND + [key, val.to_s].join(':'))
51
60
  end
@@ -67,8 +76,11 @@ module Groovy
67
76
  when Hash # { foo: 'bar' }
68
77
  conditions.each do |key, val|
69
78
  if val.is_a?(Range)
70
- parameters.push(AND + [key, val.min].join(':<=')) if val.min # gte
71
- parameters.push(AND + [key, val.max].join(':>=')) if val.max # lte
79
+ parameters.push(AND + [key, val.min].join(':<=')) if val.min > 0 # gte
80
+ parameters.push(AND + [key, val.max].join(':>=')) if val.max # lte, nil if range.max is -1
81
+ elsif val.is_a?(Array) # { foo: [1,2,3] }
82
+ str = "#{key}:!#{val.join(" OR #{key}:!")}"
83
+ parameters.push(AND + str)
72
84
  else
73
85
  parameters.push(AND + [key, val.to_s].join(':!')) # not
74
86
  end
@@ -91,10 +103,10 @@ module Groovy
91
103
  self
92
104
  end
93
105
 
94
- def paginate(page)
106
+ def paginate(page = 1)
95
107
  page = 1 if page.to_i < 1
96
108
  offset = ((page.to_i)-1) * PER_PAGE
97
- offset(offset).limit(PER_PAGE)
109
+ offset(offset).limit(PER_PAGE) # returns self
98
110
  end
99
111
 
100
112
  # sort_by(title: :asc)
@@ -105,10 +117,17 @@ module Groovy
105
117
  self
106
118
  end
107
119
 
120
+ def group_by(column)
121
+ sorting[:group_by] = column
122
+ self
123
+ end
124
+
125
+ def query
126
+ self
127
+ end
128
+
108
129
  def all
109
- @all ||= results.map do |r|
110
- model.new(r.attributes['_value']['_key'], r)
111
- end
130
+ @records || query
112
131
  end
113
132
 
114
133
  def size
@@ -118,19 +137,24 @@ module Groovy
118
137
  alias_method :count, :size
119
138
 
120
139
  def to_a
121
- all
140
+ records
122
141
  end
123
142
 
124
143
  def [](index)
125
- all[index]
144
+ records[index]
126
145
  end
127
146
 
128
147
  def each(&block)
129
- all.each { |r| block.call(r) }
148
+ records.each { |r| block.call(r) }
149
+ end
150
+
151
+ def total_entries
152
+ results # ensure query has been run
153
+ @total_entries
130
154
  end
131
155
 
132
156
  def last
133
- all[size-1]
157
+ records[size-1]
134
158
  end
135
159
 
136
160
  def in_batches(of: 1000, from: nil)
@@ -139,36 +163,56 @@ module Groovy
139
163
 
140
164
  while results.any?
141
165
  yield all
166
+ break if results.size < of
142
167
 
143
168
  @sorting[:offset] += of
144
169
  @all = @results = nil # reset
145
170
  end
146
171
  end
147
172
 
173
+ def records
174
+ @records ||= results.map do |r|
175
+ # FIXME: figure out the right way to do this.
176
+ # model.new(r.value.attributes['_key'], r)
177
+ id = r.attributes['_value']['_key']['_id']
178
+ model.new_from_record(table[id])
179
+ end
180
+ end
181
+
148
182
  private
149
183
  attr_reader :model, :table, :options
150
184
 
185
+ def clear_query
186
+ model.clear_query # sets @query to nil
187
+ end
188
+
151
189
  def results
152
190
  @results ||= execute
153
191
  rescue Groonga::TooLargeOffset
154
192
  # puts "Offset is higher than table size!"
155
193
  []
194
+ ensure
195
+ clear_query
156
196
  end
157
197
 
158
198
  def execute
159
199
  set = if parameters.any?
160
- query = parameters.join(" ").gsub(/\s(\w)/, '\ \1')
200
+ query = prepare_query
161
201
  puts query if ENV['DEBUG']
162
202
  table.select(query, options)
163
203
  else
164
204
  table.select(options)
165
205
  end
166
206
 
207
+ @total_entries = set.size
208
+
167
209
  puts "Sorting with #{sort_key_and_order}, #{sorting.inspect}" if ENV['DEBUG']
168
- set.sort(sort_key_and_order, {
210
+ set = set.sort(sort_key_and_order, {
169
211
  limit: sorting[:limit],
170
212
  offset: sorting[:offset]
171
213
  })
214
+
215
+ sorting[:group_by] ? set.group(group_by) : set
172
216
  end
173
217
 
174
218
  def map_operator(str)
@@ -185,10 +229,16 @@ module Groovy
185
229
  sorting[:by] or [{ key: @default_sort_key, order: :asc }]
186
230
  end
187
231
 
232
+ def prepare_query
233
+ query = parameters.join(" ").split(/ or /i).map do |part|
234
+ part.gsub(/\s(\w)/, '\ \1')
235
+ end.join(' OR ')
236
+ end
237
+
188
238
  def method_missing(name, *args)
189
239
  if model.respond_to?(name)
190
240
  other = model.public_send(name, *args)
191
- merge_with!(other)
241
+ merge_with!(other.query)
192
242
  else
193
243
  super
194
244
  end
@@ -25,8 +25,8 @@ module Groovy
25
25
  # UNIGRAM
26
26
 
27
27
  MAPPINGS = {
28
- 'String' => 'short_text',
29
- 'Text' => 'text',
28
+ 'String' => 'short_text', # max 4095 bytes
29
+ 'Text' => 'text', # max 65,535 bytes
30
30
  'Float' => 'float',
31
31
  'Bool' => 'boolean',
32
32
  'Boolean' => 'boolean',
@@ -2,14 +2,23 @@ module Groovy
2
2
  class Vector
3
3
  include Enumerable
4
4
 
5
+ REMOVE_MISSING = true.freeze
6
+
5
7
  def initialize(obj, key)
6
8
  @obj, @key = obj, key
7
9
  end
8
10
 
9
- def count
10
- records.count
11
+ def size
12
+ # records.count
13
+ items.count # so we filter out removed ones
14
+ end
15
+
16
+ def inspect
17
+ items.to_s
11
18
  end
12
19
 
20
+ alias_method :count, :size
21
+
13
22
  def each(&block)
14
23
  items.each { |r| block.call(r) }
15
24
  end
@@ -18,20 +27,28 @@ module Groovy
18
27
  set([])
19
28
  end
20
29
 
21
- def set(items)
22
- raise "Please save parent record first" unless obj.record
23
- obj.record[key] = items
30
+ def set(new_items)
31
+ check_parent!
32
+ obj.record[key] = new_items
33
+ end
34
+
35
+ def has?(item)
36
+ return false unless item.record
37
+ obj.record[key].find { |r| r == item.record }
24
38
  end
25
39
 
26
40
  def push(item)
27
- raise "Please save parent record first" unless obj.record
28
- obj.record[key] = obj.record[key].concat([item.record])
41
+ raise "Invalid item type: #{item.class}" unless item.is_a?(Model)
42
+ check_parent!
43
+ raise "Already in list!" if has?(item)
44
+ item.save unless item.record
45
+ push_record(item.record)
29
46
  end
30
47
 
31
48
  def remove(item)
32
- raise "Please save parent record first" unless obj.record
33
- recs = obj.record[key].delete_if { |r| r == item.record }
34
- obj.record[key] = recs
49
+ check_parent!
50
+ raise "Item not saved: #{record}" unless item.record
51
+ remove_record(item.record)
35
52
  end
36
53
 
37
54
  alias_method :<<, :push
@@ -39,9 +56,33 @@ module Groovy
39
56
  private
40
57
  attr_reader :obj, :key
41
58
 
59
+ def remove_record(rec)
60
+ recs = obj.record[key].delete_if { |r| r == rec }
61
+ obj.record[key] = recs
62
+ end
63
+
64
+ def push_record(rec)
65
+ obj.record[key] = obj.record[key].concat([rec])
66
+ end
67
+
68
+ def check_parent!
69
+ raise "Please save parent record first" unless obj.record
70
+ end
71
+
42
72
  def items
43
73
  return [] unless obj.record
44
- records.map { |r| Model.initialize_from_record(r) }
74
+ records.map do |r|
75
+ if !exists?(r)
76
+ remove_record(r) if REMOVE_MISSING
77
+ nil
78
+ else
79
+ Model.initialize_from_record(r)
80
+ end
81
+ end.compact
82
+ end
83
+
84
+ def exists?(rec)
85
+ rec.table.exist?(rec._id)
45
86
  end
46
87
 
47
88
  def records
@@ -1,3 +1,3 @@
1
1
  module Groovy
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.3'.freeze
3
3
  end
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.1.2
4
+ version: 0.1.3
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: 2019-05-02 00:00:00.000000000 Z
11
+ date: 2019-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler