groovy 0.2.6 → 0.4.0
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/relations.rb +19 -4
- data/groovy.gemspec +1 -1
- data/lib/groovy.rb +4 -4
- data/lib/groovy/model.rb +180 -77
- data/lib/groovy/query.rb +52 -25
- data/lib/groovy/schema.rb +25 -7
- data/lib/groovy/vector.rb +26 -5
- data/lib/groovy/version.rb +1 -1
- data/spec/model_spec.rb +17 -0
- data/spec/query_spec.rb +46 -1
- data/spec/search_spec.rb +54 -0
- data/spec/spec_helper.rb +3 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1019bd31fda4785bec67322dfb8aeddfab4d0fa12143b23a8dd75f4a3c84515e
|
|
4
|
+
data.tar.gz: 44ab6a5fd87d136bc3b6915ab323fac95b6a8012f5cdc4ea2fb6498d5236cb3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ae8f4bcfb8d6166fa4ad8814521c498a44f3e512de23c4cb986d7ce7cd4ef98722a889c3a8e69e1f55bdfc9651f19a9b03e9d31bcb2f87552c73c194fc8f099
|
|
7
|
+
data.tar.gz: 4a29d7007af10d67f4265ef9e431fa347ebc979745c87037fbf8957e2441fc012904cf5385d8aad8d1da6708e91ffae6b407acecf6e3c8e19f2f0bc787d90639
|
data/example/relations.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require 'bundler/setup'
|
|
2
2
|
require 'groovy'
|
|
3
3
|
|
|
4
|
-
Groovy.open('./db/
|
|
5
|
-
# Groovy.open('./db/
|
|
4
|
+
Groovy.open('./db/2020', :current)
|
|
5
|
+
# Groovy.open('./db/2021', :next)
|
|
6
6
|
|
|
7
7
|
module Groovy::Model::ClassMethods
|
|
8
8
|
def context_name
|
|
9
|
-
Time.now.year ==
|
|
9
|
+
Time.now.year == 2020 ? :current : :next
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def table
|
|
@@ -50,6 +50,21 @@ class Location
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# from groonga tests
|
|
54
|
+
#
|
|
55
|
+
# schema.create_table("Users", :type => :hash, :key_type => "UInt32") { |table| }
|
|
56
|
+
#
|
|
57
|
+
# schema.create_table("Communities", :type => :hash, :key_type => "ShortText") do |table|
|
|
58
|
+
# table.reference("users", "Users", :type => :vector)
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# groonga = @communities.add("groonga")
|
|
62
|
+
# morita = @users.add(29)
|
|
63
|
+
# groonga["users"] = [morita]
|
|
64
|
+
#
|
|
65
|
+
# assert_equal([29], @users.collect {|record| record.key})
|
|
66
|
+
# assert_equal([29], groonga["users"].collect {|record| record.key})
|
|
67
|
+
|
|
53
68
|
def insert_places(count = 1000)
|
|
54
69
|
puts "Inserting #{count} places!"
|
|
55
70
|
count.times do |i|
|
|
@@ -57,4 +72,4 @@ def insert_places(count = 1000)
|
|
|
57
72
|
end
|
|
58
73
|
end
|
|
59
74
|
|
|
60
|
-
insert_places if Place.count == 0
|
|
75
|
+
insert_places if Place.count == 0
|
data/groovy.gemspec
CHANGED
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
|
|
|
14
14
|
# s.required_rubygems_version = ">= 1.3.6"
|
|
15
15
|
# s.rubyforge_project = "groovy"
|
|
16
16
|
|
|
17
|
-
s.add_runtime_dependency "rroonga", "
|
|
17
|
+
s.add_runtime_dependency "rroonga", "= 9.0.3"
|
|
18
18
|
|
|
19
19
|
s.add_development_dependency "bundler", ">= 1.0.0"
|
|
20
20
|
s.add_development_dependency "rspec", '~> 3.0', '>= 3.0.0'
|
data/lib/groovy.rb
CHANGED
|
@@ -16,18 +16,18 @@ module Groovy
|
|
|
16
16
|
def [](name)
|
|
17
17
|
contexts[name.to_sym]
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
def first_context_name
|
|
21
21
|
contexts.keys.first
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def open(db_path, name = :default, opts = {})
|
|
25
25
|
unless db_path.is_a?(String)
|
|
26
|
-
raise ArgumentError, "Invalid db_path: #{db_path}"
|
|
26
|
+
raise ArgumentError, "Invalid db_path: #{db_path}"
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
if contexts[name.to_sym]
|
|
30
|
-
raise ArgumentError, "Context already defined: #{name}"
|
|
30
|
+
raise ArgumentError, "Context already defined: #{name}"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
contexts[name.to_sym] = if name == :default
|
|
@@ -40,7 +40,7 @@ module Groovy
|
|
|
40
40
|
def close(name = :default)
|
|
41
41
|
ctx = contexts[name.to_sym] or raise ContextNotFound.new(name)
|
|
42
42
|
contexts.delete(name.to_sym)
|
|
43
|
-
ctx.close
|
|
43
|
+
ctx.close
|
|
44
44
|
rescue Groonga::Closed => e
|
|
45
45
|
raise ContextAlreadyClosed
|
|
46
46
|
end
|
data/lib/groovy/model.rb
CHANGED
|
@@ -20,11 +20,16 @@ module Groovy
|
|
|
20
20
|
model.new_from_record(obj)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# def self.initialize_from_hash(key, obj)
|
|
24
|
+
# model = model_from_table(key)
|
|
25
|
+
# model.find(obj['_id'])
|
|
26
|
+
# end
|
|
27
|
+
|
|
23
28
|
def self.model_from_table(table_name)
|
|
24
|
-
Kernel.const_get(table_name.sub(/ies$/, 'y').sub(/s$/, ''))
|
|
29
|
+
Kernel.const_get(table_name.sub(/ies$/, 'y').sub(/s$/, '').capitalize)
|
|
25
30
|
end
|
|
26
31
|
|
|
27
|
-
def self.included(base)
|
|
32
|
+
def self.included(base)
|
|
28
33
|
base.extend(ClassMethods)
|
|
29
34
|
base.include(Forwardable)
|
|
30
35
|
base.table_name = base.name.sub(/y$/, 'ie') + 's'
|
|
@@ -44,9 +49,17 @@ module Groovy
|
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
def attribute_names
|
|
47
|
-
schema.attribute_columns
|
|
52
|
+
@attribute_names ||= schema.attribute_columns
|
|
48
53
|
end
|
|
49
54
|
|
|
55
|
+
# def singular_refs
|
|
56
|
+
# schema.singular_references
|
|
57
|
+
# end
|
|
58
|
+
|
|
59
|
+
# def plural_refs
|
|
60
|
+
# schema.plural_references
|
|
61
|
+
# end
|
|
62
|
+
|
|
50
63
|
def schema(options = {}, &block)
|
|
51
64
|
@schema ||= load_schema(options, &block)
|
|
52
65
|
end
|
|
@@ -61,63 +74,104 @@ module Groovy
|
|
|
61
74
|
s.sync
|
|
62
75
|
|
|
63
76
|
extend(PatriciaTrieMethods) if table.is_a?(Groonga::PatriciaTrie)
|
|
64
|
-
s.
|
|
65
|
-
s.singular_references.each { |col| add_ref_accessors(col) }
|
|
66
|
-
s.plural_references.each { |col| add_vector_accessors(col) }
|
|
77
|
+
s.column_names.each { |col| add_accessors_for(col, s) }
|
|
67
78
|
s
|
|
68
79
|
end
|
|
69
80
|
end
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
def add_column(name, type, options = {})
|
|
83
|
+
@attribute_names = nil # ensure cache is cleared
|
|
84
|
+
schema.column(name, type, options)
|
|
85
|
+
schema.sync
|
|
86
|
+
add_accessors_for(name)
|
|
74
87
|
end
|
|
75
88
|
|
|
76
|
-
def
|
|
77
|
-
if
|
|
78
|
-
|
|
89
|
+
def add_accessors_for(col, s = schema)
|
|
90
|
+
if s.attribute_columns.include?(col)
|
|
91
|
+
add_attr_accessors(col)
|
|
92
|
+
elsif s.singular_references.include?(col)
|
|
93
|
+
add_ref_accessors(col)
|
|
94
|
+
elsif s.plural_references.include?(col)
|
|
95
|
+
add_vector_accessors(col)
|
|
96
|
+
else
|
|
97
|
+
puts "WARNING: Unknown column type: #{col}"
|
|
79
98
|
end
|
|
80
99
|
end
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
end
|
|
101
|
+
# called from Query, so needs to be public
|
|
102
|
+
def new_from_record(record)
|
|
103
|
+
# new(record.attributes, record)
|
|
104
|
+
new(nil, record)
|
|
87
105
|
end
|
|
88
106
|
|
|
107
|
+
# def find_and_init_record(id)
|
|
108
|
+
# if found = table[id.to_i]
|
|
109
|
+
# new_from_record(found)
|
|
110
|
+
# end
|
|
111
|
+
# end
|
|
112
|
+
|
|
113
|
+
# def find_records(&block)
|
|
114
|
+
# records = table.select(&block)
|
|
115
|
+
# records.map do |r|
|
|
116
|
+
# find_and_init_record(r.attributes['_key']['_id'])
|
|
117
|
+
# end
|
|
118
|
+
# end
|
|
119
|
+
|
|
89
120
|
def find(id)
|
|
90
|
-
if record = table[id] and record.
|
|
121
|
+
if record = table[id.to_i] and record.record_id
|
|
91
122
|
new_from_record(record)
|
|
92
123
|
end
|
|
93
124
|
end
|
|
94
125
|
|
|
95
|
-
def create(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
end
|
|
126
|
+
def create(attributes, key = nil)
|
|
127
|
+
obj = new(attributes, nil, key)
|
|
128
|
+
obj.save ? obj : false
|
|
99
129
|
end
|
|
100
130
|
|
|
101
|
-
def create!(
|
|
102
|
-
create(
|
|
131
|
+
def create!(attributes, key = nil)
|
|
132
|
+
create(attributes, key) or raise Error, "Invalid"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def update_all(attrs)
|
|
136
|
+
find_each { |child| child.update_attributes(attrs) }
|
|
103
137
|
end
|
|
104
138
|
|
|
105
139
|
def delete_all
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
# find_each { |child| child.delete }
|
|
141
|
+
table.delete { |record| record._id > -1 }
|
|
108
142
|
end
|
|
109
143
|
|
|
144
|
+
# def dump_table!
|
|
145
|
+
# Groonga::TableDumper.new(table).dump
|
|
146
|
+
# # schema.rebuild!
|
|
147
|
+
# end
|
|
148
|
+
|
|
110
149
|
# def column(name)
|
|
111
150
|
# Groonga["#{table_name}.#{name}"] # .search, .similar_search, etc
|
|
112
151
|
# end
|
|
113
152
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
153
|
+
|
|
154
|
+
def index_search(column, query, options = {}, &block)
|
|
155
|
+
results = table.select { |rec| rec[column].match(query) }
|
|
156
|
+
render_results(results, &block)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def similar_search(column, query, options = {}, &block)
|
|
160
|
+
unless schema.index_columns.include?(column.to_sym)
|
|
161
|
+
raise Error, "Column '#{column}' doesn't have an index set!"
|
|
117
162
|
end
|
|
118
163
|
|
|
119
|
-
table.select
|
|
120
|
-
|
|
164
|
+
# results = table.select("#{col}:#{q}", operator: Groonga::Operation::SIMILAR)
|
|
165
|
+
results = table.select { |rec| rec[column].similar_search(query) }
|
|
166
|
+
render_results(results, &block)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def render_results(results, &block)
|
|
170
|
+
if block_given?
|
|
171
|
+
results.each { |rec| yield new_from_record(rec) }
|
|
172
|
+
else
|
|
173
|
+
results.map { |rec| new_from_record(rec) }
|
|
174
|
+
end
|
|
121
175
|
end
|
|
122
176
|
|
|
123
177
|
def unique_values_for(column, limit: -1, cache: false)
|
|
@@ -144,7 +198,7 @@ module Groovy
|
|
|
144
198
|
end
|
|
145
199
|
|
|
146
200
|
def query
|
|
147
|
-
|
|
201
|
+
query_class.new(self, table)
|
|
148
202
|
end
|
|
149
203
|
|
|
150
204
|
def scope(name, obj)
|
|
@@ -154,28 +208,25 @@ module Groovy
|
|
|
154
208
|
end
|
|
155
209
|
end
|
|
156
210
|
|
|
157
|
-
[:find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
|
211
|
+
[:find_each, :find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
|
158
212
|
define_method scope_method do |*args, &block|
|
|
159
213
|
query.public_send(scope_method, *args, &block)
|
|
160
214
|
end
|
|
161
215
|
end
|
|
162
|
-
|
|
163
|
-
# this seems to be the same as: `table[id]`
|
|
164
|
-
# def search(key, options = nil)
|
|
165
|
-
# raise "Not supported!" unless table.respond_to?(:search)
|
|
166
|
-
# table.search(key, options)
|
|
167
|
-
# end
|
|
168
216
|
|
|
169
217
|
def_instance_delegators :table, :count, :size
|
|
170
218
|
|
|
171
219
|
# called from instance too, so must by public
|
|
172
|
-
def insert(
|
|
220
|
+
def insert(attributes, key = nil)
|
|
221
|
+
set_timestamp(attributes, :created_at)
|
|
222
|
+
set_timestamp(attributes, :updated_at)
|
|
223
|
+
|
|
173
224
|
if table.support_key?
|
|
225
|
+
raise "Key required" if key.nil?
|
|
174
226
|
table.add(key, attributes)
|
|
175
|
-
else
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
table.add(key)
|
|
227
|
+
else
|
|
228
|
+
raise "Key present, but unsupported" if key
|
|
229
|
+
table.add(attributes)
|
|
179
230
|
end
|
|
180
231
|
end
|
|
181
232
|
|
|
@@ -183,6 +234,17 @@ module Groovy
|
|
|
183
234
|
obj[key_name] = Time.now if attribute_names.include?(key_name.to_sym)
|
|
184
235
|
end
|
|
185
236
|
|
|
237
|
+
def callbacks
|
|
238
|
+
@callbacks ||= {}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
[:before_create, :after_create].each do |event|
|
|
242
|
+
define_method(event) do |*method_names|
|
|
243
|
+
callbacks[:before_create] ||= []
|
|
244
|
+
callbacks[:before_create].push(*method_names)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
186
248
|
private
|
|
187
249
|
|
|
188
250
|
def query_class
|
|
@@ -194,7 +256,8 @@ module Groovy
|
|
|
194
256
|
end
|
|
195
257
|
|
|
196
258
|
def db_context
|
|
197
|
-
Groovy.contexts[context_name.to_sym]
|
|
259
|
+
Groovy.contexts[context_name.to_sym] \
|
|
260
|
+
or raise "Context not defined: #{context_name} Please call Groovy.open('./db/path') first."
|
|
198
261
|
end
|
|
199
262
|
|
|
200
263
|
def add_attr_accessors(col)
|
|
@@ -224,30 +287,32 @@ module Groovy
|
|
|
224
287
|
end
|
|
225
288
|
end
|
|
226
289
|
|
|
227
|
-
attr_reader :id, :attributes, :
|
|
290
|
+
attr_reader :id, :attributes, :record, :changes
|
|
228
291
|
|
|
229
|
-
def initialize(attrs = nil, record = nil)
|
|
230
|
-
@attributes, @
|
|
292
|
+
def initialize(attrs = nil, record = nil, key = nil)
|
|
293
|
+
@attributes, @vectors, @_key = {}, {}, key # key is used on creation only
|
|
231
294
|
|
|
232
295
|
if set_record(record)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
296
|
+
set_attributes_from_record(record)
|
|
297
|
+
else
|
|
298
|
+
attrs ||= {}
|
|
299
|
+
unless attrs.is_a?(Hash)
|
|
300
|
+
raise ArgumentError.new("Attributes should be a Hash")
|
|
301
|
+
end
|
|
238
302
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
303
|
+
# don't call set_attributes since we don't want to call
|
|
304
|
+
# setters, that might be overriden with custom logic.
|
|
305
|
+
# attrs.each { |k,v| self[k] = v }
|
|
306
|
+
set_attributes(attrs)
|
|
242
307
|
end
|
|
243
308
|
|
|
244
|
-
# don't call set_attributes since we don't want to call
|
|
245
|
-
# setters, that might be overriden with custom logic.
|
|
246
|
-
# attrs.each { |k,v| self[k] = v }
|
|
247
|
-
set_attributes(attrs)
|
|
248
309
|
@changes = {}
|
|
249
310
|
end
|
|
250
311
|
|
|
312
|
+
def inspect
|
|
313
|
+
"#<#{self.class.name} id:#{id.inspect} attributes:[#{self.class.attribute_names.join(', ')}]>"
|
|
314
|
+
end
|
|
315
|
+
|
|
251
316
|
def new_record?
|
|
252
317
|
id.nil?
|
|
253
318
|
# _key.nil?
|
|
@@ -258,10 +323,12 @@ module Groovy
|
|
|
258
323
|
end
|
|
259
324
|
|
|
260
325
|
def []=(key, val)
|
|
261
|
-
|
|
326
|
+
if self.class.schema.singular_references.include?(key.to_sym) # val.respond_to?(:record) || val.is_a?(Groonga::Record)
|
|
327
|
+
return set_ref(key, val)
|
|
328
|
+
end
|
|
262
329
|
|
|
263
330
|
unless self.class.attribute_names.include?(key.to_sym)
|
|
264
|
-
raise "Invalid attribute: #{key}"
|
|
331
|
+
raise "Invalid attribute: #{key}"
|
|
265
332
|
end
|
|
266
333
|
|
|
267
334
|
set_attribute(key, val)
|
|
@@ -299,58 +366,86 @@ module Groovy
|
|
|
299
366
|
|
|
300
367
|
def save!(options = {})
|
|
301
368
|
raise "Invalid!" unless save
|
|
369
|
+
self
|
|
302
370
|
end
|
|
303
371
|
|
|
304
372
|
def delete
|
|
305
|
-
record.delete # doesn't work if record.id doesn't match _key
|
|
306
|
-
|
|
373
|
+
# record.delete # doesn't work if record.id doesn't match _key
|
|
374
|
+
self.class.table.delete(record._id) # record.record_id
|
|
307
375
|
set_record(nil)
|
|
308
376
|
self
|
|
309
377
|
rescue Groonga::InvalidArgument => e
|
|
310
|
-
puts "Error: #{e.inspect}"
|
|
311
|
-
raise RecordNotPersisted
|
|
378
|
+
# puts "Error: #{e.inspect}"
|
|
379
|
+
raise RecordNotPersisted, e.message
|
|
312
380
|
end
|
|
313
381
|
|
|
314
382
|
def reload
|
|
383
|
+
raise RecordNotPersisted if id.nil?
|
|
315
384
|
ensure_persisted!
|
|
316
|
-
|
|
317
|
-
#
|
|
318
|
-
|
|
385
|
+
rec = self.class.table[id] # _key
|
|
386
|
+
# set_record(rec)
|
|
387
|
+
set_attributes_from_record(rec)
|
|
319
388
|
@changes = {}
|
|
320
389
|
self
|
|
321
390
|
end
|
|
322
391
|
|
|
323
392
|
def as_json(options = {})
|
|
324
|
-
attributes
|
|
393
|
+
options[:only] ? attributes.slice(*options[:only]) : attributes
|
|
325
394
|
end
|
|
326
395
|
|
|
327
396
|
def ==(other)
|
|
328
397
|
self.id == other.id
|
|
329
398
|
end
|
|
330
399
|
|
|
400
|
+
def <=>(other)
|
|
401
|
+
self.id <=> other.id
|
|
402
|
+
end
|
|
403
|
+
|
|
331
404
|
private
|
|
332
405
|
|
|
406
|
+
def get_record_attribute(key)
|
|
407
|
+
if val = record[key]
|
|
408
|
+
if self.class.schema.time_column?(key)
|
|
409
|
+
fix_time_value(val)
|
|
410
|
+
else
|
|
411
|
+
val
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def fix_time_value(val)
|
|
417
|
+
return val.to_i == 0 ? nil : val
|
|
418
|
+
end
|
|
419
|
+
|
|
333
420
|
# def _key
|
|
334
421
|
# return unless record
|
|
335
422
|
# record.respond_to?(:_key) ? record._key : id
|
|
336
423
|
# end
|
|
337
424
|
|
|
425
|
+
def set_attributes_from_record(rec)
|
|
426
|
+
self.class.attribute_names.each do |col|
|
|
427
|
+
public_send("#{col}=", get_record_attribute(col))
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
338
431
|
def set_attribute(key, val)
|
|
339
432
|
changes[key.to_sym] = [self[key], val] if changes # nil when initializing
|
|
340
433
|
attributes[key.to_sym] = val
|
|
341
434
|
end
|
|
342
435
|
|
|
343
436
|
def get_ref(name)
|
|
344
|
-
|
|
437
|
+
if record and obj = record[name]
|
|
438
|
+
Model.initialize_from_record(obj)
|
|
439
|
+
end
|
|
345
440
|
end
|
|
346
441
|
|
|
347
442
|
def set_ref(name, obj)
|
|
348
|
-
|
|
349
|
-
obj
|
|
443
|
+
if record.nil?
|
|
444
|
+
set_attribute(name, obj.id) # obj should be a groovy model or groonga record
|
|
445
|
+
else
|
|
446
|
+
obj = obj.record if obj.respond_to?(:record)
|
|
447
|
+
record[name] = obj
|
|
350
448
|
end
|
|
351
|
-
|
|
352
|
-
@refs[name] = obj
|
|
353
|
-
set_attribute(name, obj.nil? ? nil : obj.key)
|
|
354
449
|
end
|
|
355
450
|
|
|
356
451
|
def set_record(obj)
|
|
@@ -359,10 +454,18 @@ module Groovy
|
|
|
359
454
|
end
|
|
360
455
|
|
|
361
456
|
def create
|
|
362
|
-
|
|
457
|
+
fire_callbacks(:before_create)
|
|
458
|
+
set_record(self.class.insert(attributes, @_key))
|
|
459
|
+
fire_callbacks(:after_create)
|
|
363
460
|
self
|
|
364
461
|
end
|
|
365
462
|
|
|
463
|
+
def fire_callbacks(name)
|
|
464
|
+
if arr = self.class.callbacks[name] and arr.any?
|
|
465
|
+
arr.each { |fn| send(fn) }
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
366
469
|
def update
|
|
367
470
|
ensure_persisted!
|
|
368
471
|
changes.each do |key, values|
|
data/lib/groovy/query.rb
CHANGED
|
@@ -25,19 +25,28 @@ module Groovy
|
|
|
25
25
|
@default_sort_key = table.is_a?(Groonga::Hash) ? '_key' : '_id'
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# def inspect
|
|
29
|
+
# "<#{self.class.name} #{parameters}>"
|
|
30
|
+
# end
|
|
31
|
+
|
|
32
|
+
def as_json(options = {})
|
|
33
|
+
Array.new.tap do |arr|
|
|
34
|
+
each { |record| arr.push(record.as_json(options)) }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
28
38
|
def search(obj)
|
|
29
39
|
obj.each do |col, q|
|
|
30
|
-
unless model.index_columns.include?(col)
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
40
|
+
# unless model.schema.index_columns.include?(col)
|
|
41
|
+
# raise "Not an index column, so cannot do fulltext search: #{col}"
|
|
42
|
+
# end
|
|
43
|
+
q.split(' ').each do |word|
|
|
44
|
+
parameters.push(AND + "(#{col}:@#{word})")
|
|
45
|
+
end if q.is_a?(String) && q.strip != ''
|
|
34
46
|
end
|
|
47
|
+
self
|
|
35
48
|
end
|
|
36
49
|
|
|
37
|
-
# def inspect
|
|
38
|
-
# "<#{self.class.name} #{parameters}>"
|
|
39
|
-
# end
|
|
40
|
-
|
|
41
50
|
def find(id)
|
|
42
51
|
find_by(_id: id)
|
|
43
52
|
end
|
|
@@ -46,10 +55,12 @@ module Groovy
|
|
|
46
55
|
where(conditions).limit(1).first
|
|
47
56
|
end
|
|
48
57
|
|
|
49
|
-
def find_each(&block)
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
def find_each(opts = {}, &block)
|
|
59
|
+
count = 0
|
|
60
|
+
in_batches({ of: 10 }.merge(opts)) do |group|
|
|
61
|
+
group.each { |item| count += 1; yield(item) }
|
|
52
62
|
end
|
|
63
|
+
count
|
|
53
64
|
end
|
|
54
65
|
|
|
55
66
|
# http://groonga.org/docs/reference/grn_expr/query_syntax.html
|
|
@@ -74,7 +85,7 @@ module Groovy
|
|
|
74
85
|
add_param(AND + str)
|
|
75
86
|
|
|
76
87
|
else
|
|
77
|
-
str = val.nil? || val.to_s.strip == '' ? '\\' : val
|
|
88
|
+
str = val.nil? || val == false || val.to_s.strip == '' ? '\\' : escape_val(val)
|
|
78
89
|
add_param(AND + [key, str].join(':'))
|
|
79
90
|
end
|
|
80
91
|
end
|
|
@@ -108,7 +119,7 @@ module Groovy
|
|
|
108
119
|
add_param(AND + str)
|
|
109
120
|
|
|
110
121
|
else
|
|
111
|
-
str = val.nil? || val.to_s.strip == '' ? '\\' : val
|
|
122
|
+
str = val.nil? || val == false || val.to_s.strip == '' ? '\\' : escape_val(val)
|
|
112
123
|
add_param(AND + [key, str].join(':!')) # not
|
|
113
124
|
end
|
|
114
125
|
end
|
|
@@ -138,10 +149,10 @@ module Groovy
|
|
|
138
149
|
|
|
139
150
|
# sort_by(title: :asc)
|
|
140
151
|
def sort_by(hash)
|
|
141
|
-
if hash.is_a?(String) # e.g. title.desc
|
|
142
|
-
param, dir =
|
|
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('.')
|
|
143
154
|
hash = {}
|
|
144
|
-
hash[param] = dir
|
|
155
|
+
hash[param] = dir || 'asc'
|
|
145
156
|
end
|
|
146
157
|
|
|
147
158
|
sorting[:by] = hash.keys.map do |key|
|
|
@@ -181,13 +192,21 @@ module Groovy
|
|
|
181
192
|
records.each { |r| block.call(r) }
|
|
182
193
|
end
|
|
183
194
|
|
|
195
|
+
def update_all(attrs)
|
|
196
|
+
each { |r| r.update_attributes(attrs) }
|
|
197
|
+
end
|
|
198
|
+
|
|
184
199
|
def total_entries
|
|
185
200
|
results # ensure query has been run
|
|
186
201
|
@total_entries
|
|
187
202
|
end
|
|
188
203
|
|
|
189
|
-
def last
|
|
190
|
-
|
|
204
|
+
def last(count = 1)
|
|
205
|
+
if count > 1
|
|
206
|
+
records[(size-count)..-1]
|
|
207
|
+
else
|
|
208
|
+
records[size-1]
|
|
209
|
+
end
|
|
191
210
|
end
|
|
192
211
|
|
|
193
212
|
def in_batches(of: 1000, from: nil, &block)
|
|
@@ -205,9 +224,7 @@ module Groovy
|
|
|
205
224
|
|
|
206
225
|
def records
|
|
207
226
|
@records ||= results.map do |r|
|
|
208
|
-
|
|
209
|
-
id = r.attributes['_value']['_key']['_id']
|
|
210
|
-
model.find_and_init_record(id)
|
|
227
|
+
model.new_from_record(r)
|
|
211
228
|
end
|
|
212
229
|
end
|
|
213
230
|
|
|
@@ -226,25 +243,26 @@ module Groovy
|
|
|
226
243
|
def results
|
|
227
244
|
@results ||= execute
|
|
228
245
|
rescue Groonga::TooLargeOffset
|
|
229
|
-
|
|
246
|
+
puts "Offset is higher than table size!"
|
|
230
247
|
[]
|
|
231
248
|
end
|
|
232
249
|
|
|
233
250
|
def execute
|
|
234
251
|
set = if parameters.any?
|
|
235
252
|
query = prepare_query
|
|
236
|
-
|
|
253
|
+
debug "Finding records with query: #{query}"
|
|
237
254
|
table.select(query, options)
|
|
238
255
|
else
|
|
256
|
+
debug "Finding records with options: #{options.inspect}"
|
|
239
257
|
table.select(options)
|
|
240
258
|
end
|
|
241
259
|
|
|
242
260
|
@total_entries = set.size
|
|
243
261
|
|
|
244
|
-
|
|
262
|
+
debug "Sorting with #{sort_key_and_order}, #{sorting.inspect}"
|
|
245
263
|
set = set.sort(sort_key_and_order, {
|
|
246
264
|
limit: sorting[:limit],
|
|
247
|
-
offset: sorting[:offset]
|
|
265
|
+
offset: sorting[:offset], # [sorting[:offset], @total_entries].min
|
|
248
266
|
})
|
|
249
267
|
|
|
250
268
|
sorting[:group_by] ? set.group(group_by) : set
|
|
@@ -264,6 +282,10 @@ module Groovy
|
|
|
264
282
|
sorting[:by] or [{ key: @default_sort_key, order: :asc }]
|
|
265
283
|
end
|
|
266
284
|
|
|
285
|
+
def escape_val(val)
|
|
286
|
+
val.to_s.gsub(':', '\:')
|
|
287
|
+
end
|
|
288
|
+
|
|
267
289
|
def prepare_query
|
|
268
290
|
space_regex = Regexp.new('\s([' + VALID_QUERY_CHARS + '])')
|
|
269
291
|
query = parameters.join(' ').split(/ or /i).map do |part|
|
|
@@ -272,6 +294,11 @@ module Groovy
|
|
|
272
294
|
.gsub(/(\d\d):(\d\d):(\d\d)/, '\1\:\2\:\3') # escape hh:mm:ss in timestamps
|
|
273
295
|
end.join(' OR ').sub(/^-/, '_id:>0 -') #.gsub(' OR -', ' -')
|
|
274
296
|
end
|
|
297
|
+
|
|
298
|
+
def debug(str)
|
|
299
|
+
puts str if ENV['DEBUG']
|
|
300
|
+
end
|
|
301
|
+
|
|
275
302
|
end
|
|
276
303
|
|
|
277
304
|
end
|
data/lib/groovy/schema.rb
CHANGED
|
@@ -16,7 +16,9 @@ module Groovy
|
|
|
16
16
|
'boolean' => 'boolean',
|
|
17
17
|
'integer' => 'int32',
|
|
18
18
|
'big_integer' => 'int64',
|
|
19
|
-
'
|
|
19
|
+
'date' => 'date',
|
|
20
|
+
'time' => 'time',
|
|
21
|
+
'datetime' => 'time'
|
|
20
22
|
}.freeze
|
|
21
23
|
|
|
22
24
|
attr_reader :index_columns
|
|
@@ -39,15 +41,27 @@ module Groovy
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def singular_references
|
|
42
|
-
|
|
44
|
+
# @singular_references ||=
|
|
45
|
+
get_names(table.columns.select(&:reference_column?).reject(&:vector?))
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
def plural_references
|
|
46
|
-
|
|
49
|
+
# @plural_references ||=
|
|
50
|
+
get_names(table.columns.select(&:vector?))
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def attribute_columns
|
|
50
|
-
|
|
54
|
+
# @attribute_columns ||=
|
|
55
|
+
get_names(table.columns.select { |c| c.column? && !c.reference_column? && !c.vector? })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def time_columns
|
|
59
|
+
# @time_columns ||=
|
|
60
|
+
get_names(table.columns.select { |c| c.column? && c.range.name == 'Time' })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def time_column?(name)
|
|
64
|
+
time_columns.include?(name)
|
|
51
65
|
end
|
|
52
66
|
|
|
53
67
|
def rebuild!
|
|
@@ -91,6 +105,7 @@ module Groovy
|
|
|
91
105
|
@index_columns.each do |col|
|
|
92
106
|
add_index_on(col)
|
|
93
107
|
end
|
|
108
|
+
self
|
|
94
109
|
end
|
|
95
110
|
|
|
96
111
|
private
|
|
@@ -102,13 +117,15 @@ module Groovy
|
|
|
102
117
|
@table = @search_table = nil # clear cached vars
|
|
103
118
|
end
|
|
104
119
|
|
|
105
|
-
def add_index_on(col)
|
|
120
|
+
def add_index_on(col, opts = {})
|
|
106
121
|
ensure_search_table!
|
|
107
122
|
return false if search_table.have_column?([table_name, col].join('_'))
|
|
108
123
|
|
|
109
|
-
|
|
124
|
+
name_col = [table_name, col].join('.')
|
|
125
|
+
log "Adding index on #{name_col}"
|
|
110
126
|
Groonga::Schema.change_table(SEARCH_TABLE_NAME, context: context) do |table|
|
|
111
|
-
table.index(
|
|
127
|
+
# table.index(name_col, name: name_col, with_position: true, with_section: true)
|
|
128
|
+
table.index(name_col, name: name_col.sub('.', '_'))
|
|
112
129
|
end
|
|
113
130
|
end
|
|
114
131
|
|
|
@@ -117,6 +134,7 @@ module Groovy
|
|
|
117
134
|
opts = (@opts[:search_table] || {}).merge({
|
|
118
135
|
type: :patricia_trie,
|
|
119
136
|
normalizer: :NormalizerAuto,
|
|
137
|
+
key_type: "ShortText",
|
|
120
138
|
default_tokenizer: "TokenBigram"
|
|
121
139
|
})
|
|
122
140
|
log("Creating search table with options: #{opts.inspect}")
|
data/lib/groovy/vector.rb
CHANGED
|
@@ -9,16 +9,37 @@ module Groovy
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def size
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
return 0 unless obj.record
|
|
13
|
+
records.count
|
|
14
|
+
# items.count # so we filter out removed ones
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def inspect
|
|
17
|
-
|
|
18
|
+
"#<#{obj.class}->#{key.capitalize} size:#{size}>"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def as_json(options = {})
|
|
22
|
+
Array.new.tap do |arr|
|
|
23
|
+
each { |record| arr.push(record.as_json(options)) }
|
|
24
|
+
end
|
|
18
25
|
end
|
|
19
26
|
|
|
20
27
|
alias_method :count, :size
|
|
21
28
|
|
|
29
|
+
# we redefine first and last to avoid having to perform a full query
|
|
30
|
+
|
|
31
|
+
def first
|
|
32
|
+
if obj = records.first
|
|
33
|
+
Model.initialize_from_record(obj)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def last
|
|
38
|
+
if obj = records.last
|
|
39
|
+
Model.initialize_from_record(obj)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
22
43
|
def each(&block)
|
|
23
44
|
items.each { |r| block.call(r) }
|
|
24
45
|
end
|
|
@@ -71,8 +92,8 @@ module Groovy
|
|
|
71
92
|
|
|
72
93
|
def items
|
|
73
94
|
return [] unless obj.record
|
|
74
|
-
records.map do |r|
|
|
75
|
-
if !exists?(r)
|
|
95
|
+
records.map do |r|
|
|
96
|
+
if !exists?(r)
|
|
76
97
|
remove_record(r) if REMOVE_MISSING
|
|
77
98
|
nil
|
|
78
99
|
else
|
data/lib/groovy/version.rb
CHANGED
data/spec/model_spec.rb
CHANGED
|
@@ -58,6 +58,23 @@ describe Groovy::Model do
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
describe '.delete_all' do
|
|
61
|
+
|
|
62
|
+
before do
|
|
63
|
+
@first = TestProduct.create!(name: 'A product', price: 100)
|
|
64
|
+
@second = TestProduct.create!(name: 'Another product', price: 200)
|
|
65
|
+
expect(TestProduct.count).to eq(2)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'deletes all' do
|
|
69
|
+
TestProduct.delete_all
|
|
70
|
+
expect(TestProduct.count).to eq(0)
|
|
71
|
+
|
|
72
|
+
# expect { @first.reload }.to raise_error
|
|
73
|
+
# expect { @second.reload }.to raise_error
|
|
74
|
+
# expect { TestProduct.find(@first.id) }.to raise_error
|
|
75
|
+
# expect { TestProduct.find(@second.id) }.to raise_error
|
|
76
|
+
end
|
|
77
|
+
|
|
61
78
|
end
|
|
62
79
|
|
|
63
80
|
describe '#[]' do
|
data/spec/query_spec.rb
CHANGED
|
@@ -7,7 +7,7 @@ describe Groovy::Query do
|
|
|
7
7
|
load_schema! 'query_spec'
|
|
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
|
-
@p3 = TestProduct.create!(name: "Product 3", visible: true, price: 30, tag_list: nil)
|
|
10
|
+
@p3 = TestProduct.create!(name: "Product 3: The Best", visible: true, price: 30, tag_list: nil)
|
|
11
11
|
@p4 = TestProduct.create!(name: "Product 4", visible: false, price: 40, tag_list: 'one, number two')
|
|
12
12
|
@p5 = TestProduct.create!(name: "Product 5", visible: true, price: 50, tag_list: '')
|
|
13
13
|
end
|
|
@@ -17,6 +17,24 @@ describe Groovy::Query do
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
describe '#where' do
|
|
20
|
+
describe 'boolean value' do
|
|
21
|
+
it 'finds expected records' do
|
|
22
|
+
res = TestProduct.where(visible: true)
|
|
23
|
+
expect(res.map(&:id)).to eq([@p1.id, @p3.id, @p5.id])
|
|
24
|
+
|
|
25
|
+
res = TestProduct.where(visible: false)
|
|
26
|
+
expect(res.map(&:id)).to eq([@p2.id, @p4.id])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'works with other args too' do
|
|
30
|
+
res = TestProduct.where(visible: true).where(name: 'Product 5')
|
|
31
|
+
expect(res.map(&:id)).to eq([@p5.id])
|
|
32
|
+
|
|
33
|
+
res = TestProduct.where(visible: false).where(name: 'Product 2')
|
|
34
|
+
expect(res.map(&:id)).to eq([@p2.id])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
20
38
|
describe 'nil value' do
|
|
21
39
|
it 'finds expected records' do
|
|
22
40
|
res = TestProduct.where(tag_list: nil)
|
|
@@ -53,6 +71,11 @@ describe Groovy::Query do
|
|
|
53
71
|
res = TestProduct.where(tag_list: 'one, number two & three')
|
|
54
72
|
expect(res.map(&:id)).to eq([@p1.id])
|
|
55
73
|
end
|
|
74
|
+
|
|
75
|
+
it 'escapes required chars' do
|
|
76
|
+
res = TestProduct.where(name: 'Product 3: The Best')
|
|
77
|
+
expect(res.map(&:id)).to eq([@p3.id])
|
|
78
|
+
end
|
|
56
79
|
end
|
|
57
80
|
|
|
58
81
|
describe 'lower/greater than search (timestamps)' do
|
|
@@ -141,6 +164,24 @@ describe Groovy::Query do
|
|
|
141
164
|
end
|
|
142
165
|
|
|
143
166
|
describe '#not' do
|
|
167
|
+
describe 'boolean value' do
|
|
168
|
+
it 'finds expected records' do
|
|
169
|
+
res = TestProduct.where.not(visible: true)
|
|
170
|
+
expect(res.map(&:id)).to eq([@p2.id, @p4.id])
|
|
171
|
+
|
|
172
|
+
res = TestProduct.where.not(visible: false)
|
|
173
|
+
expect(res.map(&:id)).to eq([@p1.id, @p3.id, @p5.id])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'works with other args too' do
|
|
177
|
+
res = TestProduct.where.not(visible: true).where(name: 'Product 2')
|
|
178
|
+
expect(res.map(&:id)).to eq([@p2.id])
|
|
179
|
+
|
|
180
|
+
res = TestProduct.where.not(visible: false).where(name: 'Product 5')
|
|
181
|
+
expect(res.map(&:id)).to eq([@p5.id])
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
144
185
|
describe 'nil value' do
|
|
145
186
|
it 'finds expected records' do
|
|
146
187
|
res = TestProduct.where.not(tag_list: nil)
|
|
@@ -177,6 +218,10 @@ describe Groovy::Query do
|
|
|
177
218
|
res = TestProduct.not(tag_list: 'one, number two & three')
|
|
178
219
|
expect(res.map(&:id)).to eq([@p2.id, @p3.id, @p4.id, @p5.id])
|
|
179
220
|
end
|
|
221
|
+
it 'escapes required chars' do
|
|
222
|
+
res = TestProduct.not(name: 'Product 3: The Best')
|
|
223
|
+
expect(res.map(&:id)).to eq([@p1.id, @p2.id, @p4.id, @p5.id])
|
|
224
|
+
end
|
|
180
225
|
end
|
|
181
226
|
|
|
182
227
|
context 'basic regex' do
|
data/spec/search_spec.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require_relative './spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Groovy::Model, 'searching' do
|
|
4
|
+
|
|
5
|
+
before :all do
|
|
6
|
+
Groovy.open('tmp/search', 'search_spec')
|
|
7
|
+
load_schema! 'search_spec'
|
|
8
|
+
|
|
9
|
+
TestProduct.add_column :description, :string, index: true
|
|
10
|
+
|
|
11
|
+
TestProduct.create!(name: 'First product', description: 'Lorem ipsum dolor sit amet')
|
|
12
|
+
TestProduct.create!(name: 'Second product', description: 'Lorea el ipsum poh loco')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after :all do
|
|
16
|
+
Groovy.close('search_spec')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe 'single word, exact match' do
|
|
20
|
+
it 'returns results' do
|
|
21
|
+
# res = TestProduct.search(description: 'sit')
|
|
22
|
+
res = TestProduct.search(description: 'sit')
|
|
23
|
+
expect(res.first.name).to eq('First product')
|
|
24
|
+
|
|
25
|
+
# res = TestProduct.search(description: 'loco')
|
|
26
|
+
res = TestProduct.search(description: 'loco')
|
|
27
|
+
expect(res.first.name).to eq('Second product')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe 'two consecutive words, exact match' do
|
|
32
|
+
it 'returns results' do
|
|
33
|
+
# res = TestProduct.search(description: 'sit amet')
|
|
34
|
+
res = TestProduct.search(description: 'sit amet')
|
|
35
|
+
expect(res.first.name).to eq('First product')
|
|
36
|
+
|
|
37
|
+
# res = TestProduct.search(description: 'lorea el')
|
|
38
|
+
res = TestProduct.search(description: 'lorea el')
|
|
39
|
+
expect(res.first.name).to eq('Second product')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe 'two random words, both match' do
|
|
44
|
+
it 'returns results' do
|
|
45
|
+
# res = TestProduct.search(description: 'amet ipsum')
|
|
46
|
+
res = TestProduct.search(description: 'amet ipsum')
|
|
47
|
+
expect(res.first.name).to eq('First product')
|
|
48
|
+
|
|
49
|
+
# res = TestProduct.search(description: 'poh el')
|
|
50
|
+
res = TestProduct.search(description: 'poh el')
|
|
51
|
+
expect(res.first.name).to eq('Second product')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: groovy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
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:
|
|
11
|
+
date: 2020-09-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rroonga
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - '='
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 9.0.3
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- -
|
|
24
|
+
- - '='
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 9.0.3
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: bundler
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -100,6 +100,7 @@ files:
|
|
|
100
100
|
- spec/groovy_spec.rb
|
|
101
101
|
- spec/model_spec.rb
|
|
102
102
|
- spec/query_spec.rb
|
|
103
|
+
- spec/search_spec.rb
|
|
103
104
|
- spec/spec_helper.rb
|
|
104
105
|
homepage: https://github.com/tomas/groovy
|
|
105
106
|
licenses: []
|