groovy 0.6.8 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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