groovy 0.1.2 → 0.1.3

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: 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