groovy 0.6.8 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc2f1ef55cf22504bc95fd3a143108b0d7097816274606cb557851c588c7ba02
4
- data.tar.gz: 8881018c1036dab4257291bde59c8797317ba1568cafcb7d87ac12f8b7cf9499
3
+ metadata.gz: dc2860dffbf2ed0ba7df5db1d728766f8da5cd2ddd0e5bf4a07268307c54b6bb
4
+ data.tar.gz: 5175d60fd1e3ad3f78e9d87e08fd10ba90845e7d29e4e73e6214e2326bb92c2c
5
5
  SHA512:
6
- metadata.gz: 63e936c9c4920be9e27d3567acc575cf75900fcd5be9786b55f171f068dec19ab4b618ce22367ae7856c30d602517574a61fbc2fef37e36dfc62a9f9d2b7bbf6
7
- data.tar.gz: ee9f1292bb74220dd40acc1058151d4ca6f390e94b4f1b6e9c13561d6dfb1aa9a5df774d858a66695906478a70a8ba49c9ce368585496223287ad67a8276f563
6
+ metadata.gz: 57a8880213de3b723057090d0928068716ecbe8ff89b3e19867d485febcfb9af2efd998fcee011cce9e3a30cac164f6ad8612312f5824a6b962ccbd7d12e6fec
7
+ data.tar.gz: 70fce1c4f7152dbe6630d39ffe9286bf767ce1dad244636d5e8d9284b12f6c4cb31af15714c94b7da38439e3ce3404cab94543e2f8740162b199516da6115492
data/lib/groovy/model.rb CHANGED
@@ -17,6 +17,31 @@ module Groovy
17
17
  @models ||= {}
18
18
  end
19
19
 
20
+ module Utils
21
+ def self.singularize(str)
22
+ str.to_s.sub(/ies$/, 'y').sub(/s$/, '')
23
+ end
24
+
25
+ # category -> categories
26
+ # product -> products
27
+ def self.pluralize(str)
28
+ return str if str[-1] == 's' # already ends with s, assume already plural
29
+ str.to_s.sub(/y$/, 'ie') + 's'
30
+ end
31
+
32
+ def self.pluralize_and_classify(str)
33
+ classify(pluralize(str))
34
+ end
35
+
36
+ def self.underscore(str)
37
+ str.gsub(/([A-Z])/) { |x| "_#{x.downcase}" }.sub(/^_/, '')
38
+ end
39
+
40
+ def self.classify(str)
41
+ str.to_s.gsub(/([A-Z])/, '_\1').split('_').collect! { |w| w.capitalize }.join
42
+ end
43
+ end
44
+
20
45
  module Model
21
46
 
22
47
  def self.initialize_from_record(obj)
@@ -30,23 +55,21 @@ module Groovy
30
55
  # end
31
56
 
32
57
  def self.model_from_table(table_name)
33
- get_class(table_name.to_s.sub(/ies$/, 'y').sub(/s$/, ''))
58
+ get_class(Utils.singularize(table_name))
34
59
  end
35
60
 
36
61
  def self.included(base)
37
62
  base.extend(ClassMethods)
38
63
  base.include(Forwardable)
39
- base.table_name = base.name.sub(/y$/, 'ie') + 's'
64
+ base.table_name = Utils.pluralize(base.name)
40
65
 
41
66
  # add to global model list
42
67
  # Groovy.models[base.name] = base
43
68
  Groovy.models[base.table_name] = base
44
69
  end
45
70
 
46
- def self.get_class(table_name)
47
- # classify method
48
- classified = table_name.gsub(/([A-Z])/, '_\1').split('_').collect! { |w| w.capitalize }.join
49
- Kernel.const_get(classified)
71
+ def self.get_class(klass_name)
72
+ Kernel.const_get(Utils.classify(klass_name))
50
73
  end
51
74
 
52
75
  module ClassMethods
@@ -63,11 +86,15 @@ module Groovy
63
86
  end
64
87
 
65
88
  def underscore_name
66
- name.gsub(/([A-Z])/) { |x| "_#{x.downcase}" }.sub(/^_/, '')
89
+ Utils.underscore(name)
67
90
  end
68
91
 
69
92
  def attribute_names
70
- @attribute_names ||= schema.attribute_columns
93
+ schema.attribute_columns
94
+ end
95
+
96
+ def reference_names
97
+ schema.singular_references + schema.plural_references
71
98
  end
72
99
 
73
100
  # def singular_refs
@@ -87,11 +114,13 @@ module Groovy
87
114
  self.context_name = options[:context] || Groovy.first_context_name
88
115
  self.table_name = options[:table_name] if options[:table_name]
89
116
 
90
- s = Schema.new(db_context, table_name, {})
117
+ s = Schema.new(db_context, table_name, options)
91
118
  yield s if block
92
119
  s.sync
93
120
 
94
- extend(PatriciaTrieMethods) if table.is_a?(Groonga::PatriciaTrie)
121
+ include(HashTable) if table.is_a?(Groonga::Hash)
122
+ include(PatriciaTrieTable) if table.is_a?(Groonga::PatriciaTrie)
123
+
95
124
  s.column_names.each { |col| add_accessors_for(col, s) }
96
125
  s
97
126
  end
@@ -118,7 +147,7 @@ module Groovy
118
147
  elsif s.plural_references.include?(col)
119
148
  add_vector_accessors(col)
120
149
  else
121
- puts "WARNING: Unknown column type: #{col}"
150
+ raise "Unknown column type: #{col}"
122
151
  end
123
152
  end
124
153
 
@@ -147,13 +176,13 @@ module Groovy
147
176
  end
148
177
  end
149
178
 
150
- def create(attributes, key = nil)
151
- obj = new(attributes, nil, key)
179
+ def create(attributes)
180
+ obj = new(attributes)
152
181
  obj.save ? obj : false
153
182
  end
154
183
 
155
- def create!(attributes, key = nil)
156
- create(attributes, key) or raise Error, "Invalid"
184
+ def create!(attributes)
185
+ create(attributes) or raise Error, "Invalid"
157
186
  end
158
187
 
159
188
  def update_all(attrs)
@@ -174,6 +203,12 @@ module Groovy
174
203
  # Groonga["#{table_name}.#{name}"] # .search, .similar_search, etc
175
204
  # end
176
205
 
206
+ def search_table
207
+ schema.search_table
208
+ end
209
+
210
+ def_instance_delegators :search_table, :prefix_search, :each_with_prefix # just these two for now
211
+
177
212
  def index_search(column, query, options = {}, &block)
178
213
  results = table.select { |rec| rec[column].match(query) }
179
214
  render_results(results, &block)
@@ -230,6 +265,10 @@ module Groovy
230
265
  query.where(*args, &block)
231
266
  end
232
267
 
268
+ def find_or_create_by(attributes)
269
+ find_by(**attributes) || create(**attributes)
270
+ end
271
+
233
272
  [:first, :last, :select, :find_by, :search, :not, :sort_by, :in_batches, :limit, :offset, :paginate].each do |scope_method|
234
273
  define_method scope_method do |**args, &block|
235
274
  query.public_send(scope_method, **args, &block)
@@ -308,21 +347,80 @@ module Groovy
308
347
  end
309
348
  end
310
349
 
311
- module PatriciaTrieMethods
312
- extend Forwardable
313
- def_instance_delegators :table, :scan, :prefix_search, :open_prefix_cursor, :tag_keys
350
+ module PatriciaTrieTable
351
+ def self.included(base)
352
+ base.extend(ClassMethods)
353
+ end
354
+
355
+ module ClassMethods
356
+ extend Forwardable
357
+ def_instance_delegators :table, :scan, :prefix_search, :open_prefix_cursor, \
358
+ :suffix_search, :open_near_cursor, :tag_keys
359
+
360
+ def each_with_prefix(prefix, options = {}, &block)
361
+ table.open_prefix_cursor(prefix, options) do |cursor|
362
+ cursor.each { |r| yield r }
363
+ end
364
+ end
365
+
366
+ def each_near(str, options = {}, &block)
367
+ table.open_near_cursor(str, options) do |cursor|
368
+ cursor.each { |r| yield r }
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ module HashTable
375
+ def self.included(base)
376
+ base.extend(ClassMethods)
377
+ end
378
+
379
+ module ClassMethods
380
+ def find(key)
381
+ if record = table[key.to_s] and record.record_id
382
+ new_from_record(record)
383
+ end
384
+ end
385
+
386
+ def new_from_record(record)
387
+ # new(record.attributes, record)
388
+ new(record._key, nil, record)
389
+ end
390
+
391
+ def create(key, attributes)
392
+ obj = new(key, attributes)
393
+ obj.save ? obj : false
394
+ end
314
395
 
315
- def each_with_prefix(prefix, options = {}, &block)
316
- table.open_prefix_cursor(prefix, options) do |cursor|
317
- cursor.each { |r| yield r }
396
+ def create!(key, attributes)
397
+ create(key, attributes) or raise Error, "Invalid"
318
398
  end
319
399
  end
400
+
401
+ def initialize(key, attrs = nil, record = nil)
402
+ @key = key
403
+ super(attrs, record)
404
+ end
405
+
406
+ def load_record
407
+ self.class.table[key]
408
+ end
409
+
410
+ # def new_record?
411
+ # key.nil?
412
+ # end
413
+
414
+ def key
415
+ @key || (record ? record._key : nil)
416
+ end
417
+
320
418
  end
321
419
 
322
420
  attr_reader :id, :record, :changes
323
421
 
324
- def initialize(attrs = nil, record = nil, key = nil)
325
- @attributes, @vectors, @_key = {}, {}, key # key is used on creation only
422
+ def initialize(attrs = nil, record = nil)
423
+ @attributes, @vectors = {}, {}
326
424
  @foreign_refs_to_update = {}
327
425
 
328
426
  if set_record(record)
@@ -344,9 +442,9 @@ module Groovy
344
442
 
345
443
  # get reference to the actual record in the Groonga table,
346
444
  # not the temporary one we get as part of a search result.
347
- # def load_record
348
- # self.class.table[id]
349
- # end
445
+ def load_record
446
+ self.class.table[id]
447
+ end
350
448
 
351
449
  def attributes
352
450
  load_attributes_from_record # populate missing
@@ -356,7 +454,7 @@ module Groovy
356
454
  alias_method :attrs, :attributes
357
455
 
358
456
  def inspect
359
- "#<#{self.class.name} id:#{id.inspect} attributes:[#{self.class.attribute_names.join(', ')}]>"
457
+ "#<#{self.class.name} id:#{id.inspect} attributes:[#{self.class.attribute_names.join(', ')}] references:[#{self.class.reference_names.join(', ')}]>"
360
458
  end
361
459
 
362
460
  def dump
@@ -365,7 +463,6 @@ module Groovy
365
463
 
366
464
  def new_record?
367
465
  id.nil?
368
- # _key.nil?
369
466
  end
370
467
 
371
468
  def persisted?
@@ -455,7 +552,7 @@ module Groovy
455
552
  unless new_record?
456
553
  # raise RecordNotPersisted if id.nil?
457
554
  # ensure_persisted!
458
- rec = self.class.table[id] # _key
555
+ rec = load_record
459
556
  set_record(rec)
460
557
  # load_attributes_from_record
461
558
  end
@@ -494,11 +591,6 @@ module Groovy
494
591
  return val.to_i == 0 ? nil : val
495
592
  end
496
593
 
497
- # def _key
498
- # return unless record
499
- # record.respond_to?(:_key) ? record._key : id
500
- # end
501
-
502
594
  def load_attributes_from_record
503
595
  self.class.attribute_names.each do |col|
504
596
  public_send("#{col}=", get_record_attribute(col)) unless @attributes.key?(col)
@@ -545,7 +637,7 @@ module Groovy
545
637
 
546
638
  def create
547
639
  fire_callbacks(:before_create)
548
- set_record(self.class.insert(@attributes, @_key))
640
+ set_record(self.class.insert(@attributes, @key))
549
641
  update_foreign_refs
550
642
  fire_callbacks(:after_create)
551
643
  self
data/lib/groovy/query.rb CHANGED
@@ -80,20 +80,26 @@ module Groovy
80
80
  add_param(AND + "(#{map_operator(handle_timestamps(conditions))})")
81
81
  when Hash # { foo: 'bar' } or { views: 1..100 }
82
82
  conditions.each do |key, val|
83
- if val.is_a?(Range)
83
+ case val
84
+ when Model
85
+ raise "Object not persisted yet!" unless val.persisted?
86
+ str = "#{key}:#{val.id}"
87
+ add_param(AND + str)
88
+
89
+ when Range
84
90
  add_param(AND + [key, val.min].join(':>=')) if val.min # lte
85
91
  add_param(AND + [key, val.max].join(':<=')) if val.max # gte
86
92
 
87
- elsif val.is_a?(Regexp)
93
+ when Regexp
88
94
  str = val.source.gsub('/', '_slash_').gsub('(', '_openp_').gsub(')', '_closep_').gsub(REMOVE_INVALID_CHARS_REGEX, '')
89
95
  param = val.source[0] == '^' ? ':^' : val.source[-1] == '$' ? ':$' : ':~' # starts with or regexp
90
96
  add_param(AND + [key, str.downcase].join(param)) # regex must be downcase
91
97
 
92
- elsif val.is_a?(Array) # { foo: [1,2,3] }
98
+ when Array # { foo: [1,2,3] }
93
99
  str = "#{key}:#{val.join(" OR #{key}:")}"
94
100
  add_param(AND + str)
95
101
 
96
- elsif val.is_a?(Time)
102
+ when Time
97
103
  # The second, specify the timestamp as string in following format:
98
104
  # “(YEAR)/(MONTH)/(DAY) (HOUR):(MINUTE):(SECOND)”
99
105
 
data/lib/groovy/schema.rb CHANGED
@@ -4,6 +4,14 @@ module Groovy
4
4
  class Schema
5
5
 
6
6
  SEARCH_TABLE_NAME = 'Terms'.freeze
7
+
8
+ SEARCH_TABLE_OPTIONS = {
9
+ type: :patricia_trie,
10
+ normalizer: :NormalizerAuto,
11
+ key_type: "ShortText",
12
+ default_tokenizer: "TokenBigram"
13
+ }.freeze
14
+
7
15
  COLUMN_DEFAULTS = {
8
16
  compress: :zstandard
9
17
  }.freeze
@@ -33,6 +41,10 @@ module Groovy
33
41
  @table ||= context[table_name]
34
42
  end
35
43
 
44
+ def table_type
45
+ (@opts[:type] || :array).to_sym
46
+ end
47
+
36
48
  # def [](key)
37
49
  # # @spec[key.to_sym]
38
50
  # table.columns.select { |c| c.column? && c.name.to_s == "#{table_name}.#{key.to_s}" }
@@ -115,11 +127,11 @@ module Groovy
115
127
  end
116
128
 
117
129
  def reference(name, table_name = nil, options = {})
118
- table_name = "#{name}s" if table_name.nil?
130
+ table_name = Utils.pluralize_and_classify(name) if table_name.nil?
119
131
 
120
132
  unless context[table_name]
121
133
  log "Table #{table_name} doesn't exist yet! Creating now..."
122
- create_table!(table_name)
134
+ create_table!(table_name, nil)
123
135
  end
124
136
 
125
137
  refs_by_table[table_name] = name.to_sym
@@ -174,19 +186,17 @@ module Groovy
174
186
  name_col = [table_name, col].join('.')
175
187
  log "Adding index on #{name_col}"
176
188
  Groonga::Schema.change_table(SEARCH_TABLE_NAME, context: context) do |table|
177
- # table.index(name_col, name: name_col, with_position: true, with_section: true)
178
- table.index(name_col, name: name_col.sub('.', '_'))
189
+ if opts[:search] # search column requires with_position: true
190
+ table.index(name_col, name: name_col, with_position: true, with_section: true)
191
+ else
192
+ table.index(name_col, name: name_col.sub('.', '_'))
193
+ end
179
194
  end
180
195
  end
181
196
 
182
197
  def ensure_search_table!
183
198
  return if search_table
184
- opts = (@opts[:search_table] || {}).merge({
185
- type: :patricia_trie,
186
- normalizer: :NormalizerAuto,
187
- key_type: "ShortText",
188
- default_tokenizer: "TokenBigram"
189
- })
199
+ opts = (@opts[:search_table] || {}).merge(SEARCH_TABLE_OPTIONS)
190
200
  log("Creating search table with options: #{opts.inspect}")
191
201
  Groonga::Schema.create_table(SEARCH_TABLE_NAME, opts.merge(context: context))
192
202
  end
@@ -225,9 +235,9 @@ module Groovy
225
235
  end
226
236
  end
227
237
 
228
- def create_table!(name = table_name)
229
- log "Creating table #{name}!"
230
- Groonga::Schema.create_table(name, context: context)
238
+ def create_table!(name = table_name, type = table_type)
239
+ log "Creating table #{name} with type #{type.inspect}!"
240
+ Groonga::Schema.create_table(name, context: context, type: type)
231
241
  end
232
242
 
233
243
  def remove_table!(name = table_name)
@@ -1,3 +1,3 @@
1
1
  module Groovy
2
- VERSION = '0.6.8'.freeze
2
+ VERSION = '0.7.0'.freeze
3
3
  end
@@ -4,14 +4,14 @@ describe Groovy::Model do
4
4
 
5
5
  before :all do
6
6
  Groovy.open('tmp/callbacks', 'callbacks_spec')
7
- load_callbacks_schema!
7
+ load_test_schema!
8
8
  end
9
9
 
10
10
  after :all do
11
11
  Groovy.close('callbacks_spec')
12
12
  end
13
13
 
14
- def load_callbacks_schema!
14
+ def load_test_schema!
15
15
  klass = Class.new
16
16
  Object.const_set("User", klass)
17
17
  User.class_eval do
@@ -35,8 +35,6 @@ describe Groovy::Model do
35
35
  # puts "updated"
36
36
  end
37
37
  end
38
-
39
- User.add_reference :comments, "Comments", type: :vector
40
38
  end
41
39
 
42
40
  it 'fires created callbacks' do
data/spec/model_spec.rb CHANGED
@@ -15,7 +15,6 @@ describe Groovy::Model do
15
15
  end
16
16
 
17
17
  describe '.scope' do
18
-
19
18
  before :all do
20
19
  TestProduct.class_eval do
21
20
  scope :with_name, -> (name) { where(name: name) if name }
@@ -147,15 +146,12 @@ describe Groovy::Model do
147
146
  end
148
147
 
149
148
  describe 'attributes accessors' do
150
-
151
149
  end
152
150
 
153
151
  describe 'singular refs' do
154
-
155
152
  end
156
153
 
157
154
  describe 'plural refs' do
158
-
159
155
  end
160
156
 
161
157
  end
@@ -0,0 +1,74 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe Groovy::Model do
4
+
5
+ CONTEXT_NAME = 'schema_spec'
6
+
7
+ before :all do
8
+ Groovy.open('tmp/' + CONTEXT_NAME, CONTEXT_NAME)
9
+ load_test_schema!
10
+ end
11
+
12
+ after :all do
13
+ Groovy.close(CONTEXT_NAME)
14
+ end
15
+
16
+ def load_test_schema!
17
+ klass = Class.new
18
+ Object.const_set("Product", klass)
19
+ Product.class_eval do
20
+ include Groovy::Model
21
+
22
+ schema(type: 'hash', context: CONTEXT_NAME) do |t|
23
+ t.string :name
24
+ t.many :categories
25
+ t.timestamps
26
+ end
27
+ end
28
+
29
+ klass = Class.new
30
+ Object.const_set("Category", klass)
31
+ Category.class_eval do
32
+ include Groovy::Model
33
+
34
+ schema(context: CONTEXT_NAME) do |t|
35
+ t.many :products, "Products"
36
+ t.string :name
37
+ t.timestamps
38
+ end
39
+ end
40
+ end
41
+
42
+ describe 'hash table' do
43
+
44
+ it 'creates, updates and finds records as expected' do
45
+ product = Product.new('ipod-mini', name: 'iPod Mini')
46
+ expect(product.new_record?).to eq(true)
47
+
48
+ expect(product.name).to eq('iPod Mini')
49
+ expect(product.save).to eq(true)
50
+ expect(product.persisted?).to eq(true)
51
+ expect(product.key).to eq('ipod-mini')
52
+
53
+ expect(product.update_attributes(name: 'iPod Mini 2nd Generation'))
54
+ expect(product.name).to eq('iPod Mini 2nd Generation')
55
+
56
+ expect(Product.find('ipod-mini')).to eq(product)
57
+ end
58
+
59
+ it 'works with references too' do
60
+ product = Product.new('ipod-mini', name: 'iPod Mini')
61
+ expect(product.save).to eq(true)
62
+
63
+ category = Category.new(name: 'iPods')
64
+ expect(category.save).to eq(true)
65
+
66
+ category.products << product
67
+ product.categories << category
68
+
69
+ expect(product.reload.categories).to eq([category])
70
+ end
71
+
72
+ end
73
+
74
+ end
data/spec/search_spec.rb CHANGED
@@ -6,7 +6,7 @@ describe Groovy::Model, 'searching' do
6
6
  Groovy.open('tmp/search', 'search_spec')
7
7
  load_schema! 'search_spec'
8
8
 
9
- TestProduct.add_column :description, :string, index: true
9
+ TestProduct.add_column :description, :string, index: true #, search: true
10
10
 
11
11
  TestProduct.create!(name: 'First product', description: 'Lorem ipsum dolor sit amet')
12
12
  TestProduct.create!(name: 'Second product', description: 'Lorea el ipsum poh loco')
@@ -51,4 +51,25 @@ describe Groovy::Model, 'searching' do
51
51
  expect(res.first.name).to eq('Second product')
52
52
  end
53
53
  end
54
+
55
+ describe 'prefix search' do
56
+ it 'returns results' do
57
+ res = TestProduct.prefix_search('Lore')
58
+ expect(res.count).to eq(0)
59
+
60
+ res = TestProduct.prefix_search('lore')
61
+ expect(res.count).to eq(2)
62
+ expect(res.first).to be_a(Groonga::Record)
63
+ parts = res.map { |rec| rec['._key'] }
64
+ expect(parts).to eq(['lorem', 'lorea'])
65
+ end
66
+
67
+ # it 'works with each_with_prefix too' do
68
+ # parts = []
69
+ # res = TestProduct.each_with_prefix('lore') do |rec|
70
+ # parts.push(rec['._key'])
71
+ # end
72
+ # expect(parts).to eq(['lorem', 'lorea'])
73
+ # end
74
+ end
54
75
  end
data/spec/vector_spec.rb CHANGED
@@ -63,6 +63,13 @@ describe Groovy::Model do
63
63
  expect(user2.reload.comments).to eq([])
64
64
  end
65
65
 
66
+ it 'finds records using relations too' do
67
+ user = User.create(name: 'John')
68
+ comment = Comment.create(author: user, content: 'Hello there!')
69
+
70
+ expect(Comment.find_by(author: user)).to eq(comment)
71
+ end
72
+
66
73
  end
67
74
 
68
75
  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.6.8
4
+ version: 0.7.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: 2023-12-06 00:00:00.000000000 Z
11
+ date: 2023-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rroonga
@@ -101,6 +101,7 @@ files:
101
101
  - spec/groovy_spec.rb
102
102
  - spec/model_spec.rb
103
103
  - spec/query_spec.rb
104
+ - spec/schema_spec.rb
104
105
  - spec/search_spec.rb
105
106
  - spec/spec_helper.rb
106
107
  - spec/vector_spec.rb