groovy 0.6.1 → 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/groovy/model.rb +84 -10
- data/lib/groovy/query.rb +0 -1
- data/lib/groovy/schema.rb +45 -8
- data/lib/groovy/vector.rb +56 -26
- data/lib/groovy/version.rb +1 -1
- data/spec/callbacks_spec.rb +58 -0
- data/spec/vector_spec.rb +15 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc2a1c85a6d482813b6ec9de3616e2c369206a419008b55600194f930bc56416
|
4
|
+
data.tar.gz: 968d357fb616dcfa9761f1ba93843678263d69ff38af2ec342ee9430bbf7ccc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7df3be7e3c43b5d48be0b563bb156da21a8992c97be9392b91b939a1943920f0587ee01ff4521c983b8524beb27b0ecd0eede630aff7c16e42e80e004ec42a46
|
7
|
+
data.tar.gz: e9a7fd6abc2dda7d902cd07d091f1fafbfe053e229a730754f0170dc04aa3326fbbef4a9646f3d6788cf896b7b72e9c0c10f0c010a4d17bd1e99aa97222fcda9
|
data/lib/groovy/model.rb
CHANGED
@@ -13,6 +13,10 @@ module Groovy
|
|
13
13
|
class Error < StandardError; end
|
14
14
|
class RecordNotPersisted < Error; end
|
15
15
|
|
16
|
+
def self.models
|
17
|
+
@models ||= {}
|
18
|
+
end
|
19
|
+
|
16
20
|
module Model
|
17
21
|
|
18
22
|
def self.initialize_from_record(obj)
|
@@ -33,9 +37,14 @@ module Groovy
|
|
33
37
|
base.extend(ClassMethods)
|
34
38
|
base.include(Forwardable)
|
35
39
|
base.table_name = base.name.sub(/y$/, 'ie') + 's'
|
40
|
+
|
41
|
+
# add to global model list
|
42
|
+
# Groovy.models[base.name] = base
|
43
|
+
Groovy.models[base.table_name] = base
|
36
44
|
end
|
37
45
|
|
38
46
|
def self.get_class(table_name)
|
47
|
+
# classify method
|
39
48
|
classified = table_name.gsub(/([A-Z])/, '_\1').split('_').collect! { |w| w.capitalize }.join
|
40
49
|
Kernel.const_get(classified)
|
41
50
|
end
|
@@ -53,6 +62,10 @@ module Groovy
|
|
53
62
|
db_context[table_name]
|
54
63
|
end
|
55
64
|
|
65
|
+
def underscore_name
|
66
|
+
name.gsub(/([A-Z])/) { |x| "_#{x.downcase}" }.sub(/^_/, '')
|
67
|
+
end
|
68
|
+
|
56
69
|
def attribute_names
|
57
70
|
@attribute_names ||= schema.attribute_columns
|
58
71
|
end
|
@@ -253,10 +266,14 @@ module Groovy
|
|
253
266
|
@callbacks ||= {}
|
254
267
|
end
|
255
268
|
|
256
|
-
[:before_create, :after_create].each do |event|
|
257
|
-
define_method(event) do |*
|
258
|
-
callbacks[
|
259
|
-
|
269
|
+
[:before_create, :after_create, :before_update, :after_update].each do |event|
|
270
|
+
define_method(event) do |*args, &block|
|
271
|
+
callbacks[event] ||= []
|
272
|
+
if block
|
273
|
+
callbacks[event].push(block)
|
274
|
+
else
|
275
|
+
callbacks[event].push(*args)
|
276
|
+
end
|
260
277
|
end
|
261
278
|
end
|
262
279
|
|
@@ -306,6 +323,7 @@ module Groovy
|
|
306
323
|
|
307
324
|
def initialize(attrs = nil, record = nil, key = nil)
|
308
325
|
@attributes, @vectors, @_key = {}, {}, key # key is used on creation only
|
326
|
+
@foreign_refs_to_update = {}
|
309
327
|
|
310
328
|
if set_record(record)
|
311
329
|
# load_attributes_from_record(record)
|
@@ -344,6 +362,10 @@ module Groovy
|
|
344
362
|
# _key.nil?
|
345
363
|
end
|
346
364
|
|
365
|
+
def persisted?
|
366
|
+
!new_record?
|
367
|
+
end
|
368
|
+
|
347
369
|
def [](key)
|
348
370
|
k = key.to_sym
|
349
371
|
@attributes[k] = get_record_attribute(k) unless @attributes.key?(k)
|
@@ -377,6 +399,18 @@ module Groovy
|
|
377
399
|
obj.each { |k,v| public_send("#{k}=", v) }
|
378
400
|
end
|
379
401
|
|
402
|
+
def set_reverse_reference(key, obj, removing = false)
|
403
|
+
if self.class.schema.singular_references.include?(key.to_sym)
|
404
|
+
# puts "setting #{obj} as #{key}"
|
405
|
+
set_ref(key, removing ? nil : obj, true)
|
406
|
+
elsif self.class.schema.plural_references.include?(key.to_sym)
|
407
|
+
# puts "adding #{obj} to #{key}"
|
408
|
+
public_send(key).set_ref(obj, removing)
|
409
|
+
else
|
410
|
+
raise "Invalid reference name: #{name}"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
380
414
|
def update_attributes(obj)
|
381
415
|
set_attributes(obj)
|
382
416
|
return false if respond_to?(:invalid?) and invalid?
|
@@ -392,7 +426,7 @@ module Groovy
|
|
392
426
|
if new_record?
|
393
427
|
create && true
|
394
428
|
else
|
395
|
-
update
|
429
|
+
update && true
|
396
430
|
end
|
397
431
|
end
|
398
432
|
|
@@ -422,6 +456,7 @@ module Groovy
|
|
422
456
|
|
423
457
|
@attributes = {}
|
424
458
|
@changes = {}
|
459
|
+
@foreign_refs_to_update = {}
|
425
460
|
self
|
426
461
|
end
|
427
462
|
|
@@ -439,6 +474,7 @@ module Groovy
|
|
439
474
|
|
440
475
|
private
|
441
476
|
|
477
|
+
|
442
478
|
def get_record_attribute(key)
|
443
479
|
return if record.nil?
|
444
480
|
val = record[key]
|
@@ -475,12 +511,26 @@ module Groovy
|
|
475
511
|
end
|
476
512
|
end
|
477
513
|
|
478
|
-
|
479
|
-
|
514
|
+
|
515
|
+
def set_ref(name, obj, from_foreign = false)
|
516
|
+
# puts "Setting ref #{name} -> #{obj} (#{obj.class})"
|
517
|
+
|
518
|
+
if !from_foreign && obj.is_a?(Model) && !obj.new_record?
|
519
|
+
if foreign_ref = self.class.schema.reverse_reference_of(name.to_sym)
|
520
|
+
# puts "Found circular ref (#{name} -> #{foreign_ref})"
|
521
|
+
if current = get_ref(name)
|
522
|
+
current.set_reverse_reference(foreign_ref, self, true)
|
523
|
+
end
|
524
|
+
|
525
|
+
@foreign_refs_to_update[name] = [foreign_ref, obj]
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
if record.nil? # not persisted yet
|
480
530
|
set_attribute(name, obj.id) # obj should be a groovy model or groonga record
|
481
531
|
else
|
482
|
-
|
483
|
-
record[name] =
|
532
|
+
val = obj.respond_to?(:record) ? obj.record : obj
|
533
|
+
record[name] = val
|
484
534
|
end
|
485
535
|
end
|
486
536
|
|
@@ -492,17 +542,28 @@ module Groovy
|
|
492
542
|
def create
|
493
543
|
fire_callbacks(:before_create)
|
494
544
|
set_record(self.class.insert(@attributes, @_key))
|
545
|
+
update_foreign_refs
|
495
546
|
fire_callbacks(:after_create)
|
496
547
|
self
|
497
548
|
end
|
498
549
|
|
499
550
|
def fire_callbacks(name)
|
551
|
+
# puts "Firing #{name} callbacks"
|
500
552
|
if arr = self.class.callbacks[name] and arr.any?
|
501
|
-
arr.each { |fn|
|
553
|
+
arr.each { |fn| fire_callback(fn) }
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def fire_callback(fn)
|
558
|
+
if fn.is_a?(Proc)
|
559
|
+
fn.call(self)
|
560
|
+
else
|
561
|
+
send(fn)
|
502
562
|
end
|
503
563
|
end
|
504
564
|
|
505
565
|
def update
|
566
|
+
fire_callbacks(:before_update)
|
506
567
|
ensure_persisted!
|
507
568
|
changes.each do |key, values|
|
508
569
|
# puts "Updating #{key} from #{values[0]} to #{values[1]}"
|
@@ -510,9 +571,22 @@ module Groovy
|
|
510
571
|
end
|
511
572
|
self.class.set_timestamp(record, :updated_at)
|
512
573
|
changes = {}
|
574
|
+
update_foreign_refs
|
575
|
+
fire_callbacks(:after_update)
|
513
576
|
self
|
514
577
|
end
|
515
578
|
|
579
|
+
def update_foreign_refs
|
580
|
+
return if @foreign_refs_to_update.empty?
|
581
|
+
|
582
|
+
@foreign_refs_to_update.each do |attr, (ref, obj)|
|
583
|
+
# puts "setting #{obj.class}##{ref} to #{self}"
|
584
|
+
obj.set_reverse_reference(ref, self)
|
585
|
+
end
|
586
|
+
|
587
|
+
@foreign_refs_to_update = {}
|
588
|
+
end
|
589
|
+
|
516
590
|
def ensure_persisted!
|
517
591
|
raise "Not persisted" if new_record?
|
518
592
|
end
|
data/lib/groovy/query.rb
CHANGED
data/lib/groovy/schema.rb
CHANGED
@@ -33,6 +33,11 @@ module Groovy
|
|
33
33
|
@table ||= context[table_name]
|
34
34
|
end
|
35
35
|
|
36
|
+
# def [](key)
|
37
|
+
# # @spec[key.to_sym]
|
38
|
+
# table.columns.select { |c| c.column? && c.name.to_s == "#{table_name}.#{key.to_s}" }
|
39
|
+
# end
|
40
|
+
|
36
41
|
def search_table
|
37
42
|
@cache[:search_table] ||= context[SEARCH_TABLE_NAME]
|
38
43
|
end
|
@@ -69,6 +74,22 @@ module Groovy
|
|
69
74
|
get_names(table.columns.select { |c| c.column? && c.range.name == type })
|
70
75
|
end
|
71
76
|
|
77
|
+
def refs_by_table
|
78
|
+
@refs_by_table ||= {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def refs_by_name
|
82
|
+
@refs_by_name ||= {}
|
83
|
+
end
|
84
|
+
|
85
|
+
def reverse_reference_of(ref_name)
|
86
|
+
if ref_table = refs_by_name[ref_name.to_sym]
|
87
|
+
if foreign_model = Groovy.models[ref_table]
|
88
|
+
foreign_model.schema.refs_by_table[@table_name]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
72
93
|
def reload
|
73
94
|
@cache = {}
|
74
95
|
end
|
@@ -95,9 +116,25 @@ module Groovy
|
|
95
116
|
|
96
117
|
def reference(name, table_name = nil, options = {})
|
97
118
|
table_name = "#{name}s" if table_name.nil?
|
119
|
+
|
120
|
+
unless context[table_name]
|
121
|
+
log "Table #{table_name} doesn't exist yet! Creating now..."
|
122
|
+
create_table!(table_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
refs_by_table[table_name] = name.to_sym
|
126
|
+
refs_by_name[name.to_sym] = table_name
|
98
127
|
@spec[name.to_sym] = { type: :reference, args: [table_name, options] }
|
99
128
|
end
|
100
129
|
|
130
|
+
def one(name, table_name = nil, options = {})
|
131
|
+
reference(name, table_name, options)
|
132
|
+
end
|
133
|
+
|
134
|
+
def many(name, table_name = nil, options = {})
|
135
|
+
reference(name, table_name, options.merge(type: :vector))
|
136
|
+
end
|
137
|
+
|
101
138
|
def timestamps(opts = {})
|
102
139
|
column(:created_at, 'time')
|
103
140
|
column(:updated_at, 'time')
|
@@ -188,15 +225,15 @@ module Groovy
|
|
188
225
|
end
|
189
226
|
end
|
190
227
|
|
191
|
-
def create_table!
|
192
|
-
log "Creating table #{
|
193
|
-
Groonga::Schema.create_table(
|
228
|
+
def create_table!(name = table_name)
|
229
|
+
log "Creating table #{name}!"
|
230
|
+
Groonga::Schema.create_table(name, context: context)
|
194
231
|
end
|
195
232
|
|
196
|
-
def remove_table!
|
197
|
-
log "Removing table #{
|
198
|
-
Groonga::Schema.remove_table(
|
199
|
-
@table = nil
|
233
|
+
def remove_table!(name = table_name)
|
234
|
+
log "Removing table #{name}!"
|
235
|
+
Groonga::Schema.remove_table(name, context: context)
|
236
|
+
@table = nil if name == table_name
|
200
237
|
end
|
201
238
|
|
202
239
|
def get_names(columns)
|
@@ -204,7 +241,7 @@ module Groovy
|
|
204
241
|
end
|
205
242
|
|
206
243
|
def log(str)
|
207
|
-
puts "[#{table_name}] #{str}"
|
244
|
+
puts "[#{table_name}] #{str}" if ENV['DEBUG']
|
208
245
|
end
|
209
246
|
end
|
210
247
|
end
|
data/lib/groovy/vector.rb
CHANGED
@@ -26,20 +26,6 @@ module Groovy
|
|
26
26
|
|
27
27
|
alias_method :count, :size
|
28
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
|
-
|
43
29
|
def each(&block)
|
44
30
|
items.each { |r| block.call(r) }
|
45
31
|
end
|
@@ -50,7 +36,9 @@ module Groovy
|
|
50
36
|
|
51
37
|
def set(new_items)
|
52
38
|
check_parent!
|
53
|
-
obj.record[key] = new_items
|
39
|
+
obj.record[key] = new_items.map do |obj|
|
40
|
+
obj.is_a?(Model) ? obj.record : obj
|
41
|
+
end
|
54
42
|
end
|
55
43
|
|
56
44
|
def has?(item)
|
@@ -61,25 +49,62 @@ module Groovy
|
|
61
49
|
def push(item)
|
62
50
|
raise "Invalid item type: #{item.class}" unless item.is_a?(Model)
|
63
51
|
check_parent!
|
64
|
-
|
52
|
+
if has?(item)
|
53
|
+
# puts "Item already present #{item.inspect}"
|
54
|
+
return false
|
55
|
+
end
|
65
56
|
item.save unless item.record
|
66
57
|
push_record(item.record)
|
58
|
+
# size
|
59
|
+
end
|
60
|
+
|
61
|
+
def push!(item)
|
62
|
+
push or raise "Already in list!"
|
67
63
|
end
|
68
64
|
|
69
65
|
def remove(item)
|
70
66
|
check_parent!
|
71
|
-
raise "Item not saved: #{
|
67
|
+
raise "Item not saved: #{item.inspect}" unless item.record
|
72
68
|
remove_record(item.record)
|
73
69
|
end
|
74
70
|
|
71
|
+
def set_ref(item, removing = false)
|
72
|
+
check_parent!
|
73
|
+
raise "Item not saved: #{item.inspect}" unless item.record
|
74
|
+
|
75
|
+
if removing
|
76
|
+
remove_record(item.record)
|
77
|
+
elsif !has?(item)
|
78
|
+
push_record(item.record)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
75
82
|
alias_method :<<, :push
|
76
83
|
|
77
|
-
[:
|
84
|
+
[:new, :create, :create!].each do |relation_method|
|
85
|
+
define_method relation_method do |attrs|
|
86
|
+
vector_model.public_send(relation_method, attrs.merge(object_name => obj))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
[:find_by, :search, :where, :not, :sort_by, :limit, :offset, :paginate, :in_batches].each do |scope_method|
|
78
91
|
define_method scope_method do |*args, &block|
|
79
92
|
query.public_send(scope_method, *args, &block)
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
96
|
+
def first
|
97
|
+
if obj = records.first
|
98
|
+
Model.initialize_from_record(obj)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def last
|
103
|
+
if obj = records.last
|
104
|
+
Model.initialize_from_record(obj)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
83
108
|
def ==(arr)
|
84
109
|
to_a == arr
|
85
110
|
end
|
@@ -87,21 +112,26 @@ module Groovy
|
|
87
112
|
private
|
88
113
|
attr_reader :obj, :key
|
89
114
|
|
90
|
-
def query
|
91
|
-
model = Model.model_from_table(key.capitalize)
|
92
|
-
obj_name = obj.class.name.downcase
|
93
|
-
model.query.where(obj_name => obj.id)
|
94
|
-
end
|
95
|
-
|
96
115
|
def method_missing(name, *args, &block)
|
97
|
-
|
98
|
-
if model.scopes.include?(name)
|
116
|
+
if vector_model.scopes.include?(name)
|
99
117
|
query.send(name)
|
100
118
|
else
|
101
119
|
super
|
102
120
|
end
|
103
121
|
end
|
104
122
|
|
123
|
+
def query
|
124
|
+
vector_model.query.where({ object_name => obj.id })
|
125
|
+
end
|
126
|
+
|
127
|
+
def object_name
|
128
|
+
obj.class.underscore_name # eg "product_brand"
|
129
|
+
end
|
130
|
+
|
131
|
+
def vector_model
|
132
|
+
@vector_model ||= Model.model_from_table(key.capitalize)
|
133
|
+
end
|
134
|
+
|
105
135
|
def remove_record(rec)
|
106
136
|
recs = obj.record[key].delete_if { |r| r == rec }
|
107
137
|
obj.record[key] = recs
|
data/lib/groovy/version.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative './spec_helper'
|
2
|
+
|
3
|
+
describe Groovy::Model do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
Groovy.open('tmp/callbacks', 'callbacks_spec')
|
7
|
+
load_callbacks_schema!
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
Groovy.close('callbacks_spec')
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_callbacks_schema!
|
15
|
+
klass = Class.new
|
16
|
+
Object.const_set("User", klass)
|
17
|
+
User.class_eval do
|
18
|
+
include Groovy::Model
|
19
|
+
|
20
|
+
after_create :created!
|
21
|
+
after_update do |record|
|
22
|
+
record.updated!
|
23
|
+
end
|
24
|
+
|
25
|
+
schema(context: 'callbacks_spec') do |t|
|
26
|
+
t.string :name
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
|
30
|
+
def created!
|
31
|
+
# puts "created"
|
32
|
+
end
|
33
|
+
|
34
|
+
def updated!
|
35
|
+
# puts "updated"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
User.add_reference :comments, "Comments", type: :vector
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'fires created callbacks' do
|
43
|
+
user = User.new(name: 'John')
|
44
|
+
expect(user).to receive(:created!)
|
45
|
+
expect(user).not_to receive(:updated!)
|
46
|
+
expect(user.save).to eq(true)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'fires updated callbacks' do
|
50
|
+
user = User.new(name: 'John')
|
51
|
+
user.save
|
52
|
+
user.name = 'Josh'
|
53
|
+
expect(user).not_to receive(:created!)
|
54
|
+
expect(user).to receive(:updated!)
|
55
|
+
expect(user.save).to eq(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/spec/vector_spec.rb
CHANGED
@@ -22,7 +22,7 @@ describe Groovy::Model do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
klass = Class.new
|
25
|
+
klass = Class.new
|
26
26
|
Object.const_set("User", klass)
|
27
27
|
User.class_eval do
|
28
28
|
include Groovy::Model
|
@@ -42,13 +42,25 @@ describe Groovy::Model do
|
|
42
42
|
load_vector_schema!
|
43
43
|
end
|
44
44
|
|
45
|
-
it 'loads records and
|
45
|
+
it 'loads records and adds reverse relations automatically' do
|
46
46
|
user = User.new(name: 'John')
|
47
47
|
expect(user.save).to eq(true)
|
48
48
|
comment = Comment.new(author: user, content: 'Hello there!')
|
49
49
|
expect(comment.save).to eq(true)
|
50
|
-
user.comments << comment
|
50
|
+
# user.comments << comment
|
51
51
|
expect(user.reload.comments).to eq([comment])
|
52
|
+
|
53
|
+
user2 = User.new(name: 'Paul')
|
54
|
+
expect(user2.save).to eq(true)
|
55
|
+
|
56
|
+
comment.author = user2
|
57
|
+
comment.save
|
58
|
+
|
59
|
+
expect(user.reload.comments).to eq([])
|
60
|
+
expect(user2.reload.comments).to eq([comment])
|
61
|
+
|
62
|
+
comment.delete
|
63
|
+
expect(user2.reload.comments).to eq([])
|
52
64
|
end
|
53
65
|
|
54
66
|
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.
|
4
|
+
version: 0.6.6
|
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-
|
11
|
+
date: 2023-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rroonga
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/groovy/types.rb
|
98
98
|
- lib/groovy/vector.rb
|
99
99
|
- lib/groovy/version.rb
|
100
|
+
- spec/callbacks_spec.rb
|
100
101
|
- spec/groovy_spec.rb
|
101
102
|
- spec/model_spec.rb
|
102
103
|
- spec/query_spec.rb
|