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 +4 -4
- data/example/Gemfile.lock +1 -1
- data/example/basic.rb +1 -1
- data/example/relations.rb +11 -2
- data/lib/groovy/model.rb +74 -30
- data/lib/groovy/query.rb +64 -14
- data/lib/groovy/types.rb +2 -2
- data/lib/groovy/vector.rb +52 -11
- data/lib/groovy/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a5a7078b71e609451ea64d58ee47e7573f7570563118c1b1fdf62078617ce8d
|
4
|
+
data.tar.gz: 7444d38fc53884cb3540c0ab73efd91f296fcfa96828890cc29509831e6ae656
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c20a741e4074aae0e28164ed307080229257d9c1fb6e00bd9fbe50f7b558b0189c1172cadc0ecd9b56484724b442a9bdd5bd59e67fbf748bdee9f4c39642fbd0
|
7
|
+
data.tar.gz: e6481be3ce2ae9fc87071bf1865e1672e7da9b3af0eee39a69dc718be6c4ef39b5dbb942cdd466fe26ccc2035fa5aba70037c12a2f373b44de42d88e3d7cc4b6
|
data/example/Gemfile.lock
CHANGED
data/example/basic.rb
CHANGED
data/example/relations.rb
CHANGED
@@ -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
|
data/lib/groovy/model.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
129
|
+
query
|
108
130
|
end
|
109
131
|
|
110
|
-
def first(
|
111
|
-
arr = limit(
|
112
|
-
|
132
|
+
def first(num = 1)
|
133
|
+
arr = limit(num)
|
134
|
+
num == 1 ? arr.first : arr
|
113
135
|
end
|
114
136
|
|
115
|
-
def last(
|
116
|
-
arr = limit(
|
117
|
-
|
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
|
-
|
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(
|
214
|
+
def initialize(attrs = nil, record = nil)
|
195
215
|
@attributes, @refs, @vectors = {}, {}, {}
|
196
|
-
|
197
|
-
if
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
267
|
-
record = self.class.table[
|
268
|
-
|
269
|
-
|
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
|
-
|
338
|
+
set_record(self.class.insert(attributes))
|
299
339
|
self
|
300
340
|
end
|
301
341
|
|
302
342
|
def update
|
303
|
-
|
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
|
data/lib/groovy/query.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
140
|
+
records
|
122
141
|
end
|
123
142
|
|
124
143
|
def [](index)
|
125
|
-
|
144
|
+
records[index]
|
126
145
|
end
|
127
146
|
|
128
147
|
def each(&block)
|
129
|
-
|
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
|
-
|
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 =
|
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
|
data/lib/groovy/types.rb
CHANGED
data/lib/groovy/vector.rb
CHANGED
@@ -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
|
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(
|
22
|
-
|
23
|
-
obj.record[key] =
|
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 "
|
28
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
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
|
data/lib/groovy/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2019-05-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|