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