shoden 0.3.0 → 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/.travis.yml +14 -0
- data/lib/shoden.rb +156 -54
- data/shoden.gemspec +1 -1
- data/test/shoden_test.rb +41 -25
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28a3009afa5b70167a8c9128a356002e11224695
|
4
|
+
data.tar.gz: 77efc3fd3a51f5d98ad27591657e96e56f7b9628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ae31056c6817127730fdebeddd82ecf8cbff424c9dbedef97dd1674825a421c4b9725670ddaf5094828703fdb0214de600011b718aa5bdbfa4bb0c7dc837853
|
7
|
+
data.tar.gz: 72d6ca3fa679e24c8201ba4231228f56f6a59c6b4f399b8ec70b9566b9eda835c00750759402ceb126d684b87095d48a28d0c811422b06c7c97b6b06f9bb2e74
|
data/.travis.yml
ADDED
data/lib/shoden.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sequel'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
Sequel.extension :pg_hstore, :pg_hstore_ops
|
4
5
|
|
@@ -10,10 +11,45 @@ module Shoden
|
|
10
11
|
|
11
12
|
Proxy = Struct.new(:klass, :parent) do
|
12
13
|
def create(args = {})
|
13
|
-
key
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
klass.create(args.merge(key => parent.id))
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
klass.filter(parent_filter)
|
19
|
+
end
|
20
|
+
|
21
|
+
def count
|
22
|
+
klass.count
|
23
|
+
end
|
24
|
+
|
25
|
+
def any?
|
26
|
+
count > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def first
|
30
|
+
filter = { order: "id ASC LIMIT 1" }.merge!(parent_filter)
|
31
|
+
klass.filter(filter).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def last
|
35
|
+
filter = { order: "id DESC LIMIT 1" }.merge!(parent_filter)
|
36
|
+
klass.filter(filter).first
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](id)
|
40
|
+
filter = { id: id }.merge!(parent_filter)
|
41
|
+
|
42
|
+
klass.filter(filter).first
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def parent_filter
|
48
|
+
{ key => parent.id }
|
49
|
+
end
|
50
|
+
|
51
|
+
def key
|
52
|
+
"#{parent.class.to_reference}_id".freeze
|
17
53
|
end
|
18
54
|
end
|
19
55
|
|
@@ -25,14 +61,28 @@ module Shoden
|
|
25
61
|
@_url ||= ENV['DATABASE_URL']
|
26
62
|
end
|
27
63
|
|
28
|
-
def self.
|
29
|
-
@
|
64
|
+
def self.models
|
65
|
+
@_models ||= Set.new
|
30
66
|
end
|
31
67
|
|
32
|
-
def self.
|
33
|
-
|
34
|
-
|
68
|
+
def self.connection
|
69
|
+
loggers = []
|
70
|
+
|
71
|
+
if ENV["DEBUG"]
|
72
|
+
require 'logger'
|
73
|
+
loggers << Logger.new($stdout)
|
35
74
|
end
|
75
|
+
|
76
|
+
@_connection ||= Sequel.connect(url, loggers: loggers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.setup
|
80
|
+
connection.execute("CREATE EXTENSION IF NOT EXISTS hstore")
|
81
|
+
models.each { |m| m.setup }
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.destroy_tables
|
85
|
+
models.each { |m| m.destroy_table }
|
36
86
|
end
|
37
87
|
|
38
88
|
class Model
|
@@ -43,12 +93,12 @@ module Shoden
|
|
43
93
|
end
|
44
94
|
|
45
95
|
def id
|
46
|
-
|
96
|
+
return nil if !defined?(@_id)
|
47
97
|
@_id.to_i
|
48
98
|
end
|
49
99
|
|
50
100
|
def destroy
|
51
|
-
lookup(id).delete
|
101
|
+
self.class.lookup(id).delete
|
52
102
|
end
|
53
103
|
|
54
104
|
def update(attrs = {})
|
@@ -61,32 +111,47 @@ module Shoden
|
|
61
111
|
end
|
62
112
|
|
63
113
|
def save
|
64
|
-
|
65
|
-
table.where(id: @_id).update data: sanitized_attrs
|
66
|
-
else
|
67
|
-
begin
|
68
|
-
@_id = table.insert data: sanitized_attrs
|
69
|
-
rescue Sequel::UniqueConstraintViolation
|
70
|
-
raise UniqueIndexViolation
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
self.class.indices.each { |i| create_index(i) }
|
75
|
-
self.class.uniques.each { |i| create_index(i, :unique) }
|
76
|
-
|
114
|
+
self.class.save(self)
|
77
115
|
self
|
78
116
|
end
|
79
117
|
|
80
118
|
def load!
|
81
|
-
ret = lookup(@_id)
|
119
|
+
ret = self.class.lookup(@_id)
|
120
|
+
return nil if ret.nil?
|
82
121
|
update(ret.to_a.first[:data])
|
83
122
|
self
|
84
123
|
end
|
85
124
|
|
125
|
+
def self.inherited(model)
|
126
|
+
Shoden.models.add(model)
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.save(record)
|
130
|
+
if record.id
|
131
|
+
table.where(id: record.id).update(data: record.attributes)
|
132
|
+
else
|
133
|
+
begin
|
134
|
+
id = table.insert(data: record.attributes)
|
135
|
+
record.instance_variable_set(:@_id, id)
|
136
|
+
rescue Sequel::UniqueConstraintViolation
|
137
|
+
raise UniqueIndexViolation
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
86
142
|
def self.all
|
87
143
|
collect
|
88
144
|
end
|
89
145
|
|
146
|
+
def self.count
|
147
|
+
size = 0
|
148
|
+
Shoden.connection.fetch("SELECT COUNT(*) FROM \"#{table_name}\"") do |r|
|
149
|
+
size = r[:count]
|
150
|
+
end
|
151
|
+
|
152
|
+
size
|
153
|
+
end
|
154
|
+
|
90
155
|
def self.first
|
91
156
|
collect("ORDER BY id ASC LIMIT 1").first
|
92
157
|
end
|
@@ -152,15 +217,57 @@ module Shoden
|
|
152
217
|
end
|
153
218
|
end
|
154
219
|
|
220
|
+
def self.filter(conditions = {})
|
221
|
+
query = []
|
222
|
+
id = conditions.delete(:id)
|
223
|
+
order = conditions.delete(:order)
|
224
|
+
|
225
|
+
if id && !conditions.any?
|
226
|
+
rows = table.where(id: id)
|
227
|
+
else
|
228
|
+
conditions.each { |k,v| query << "data->'#{k}' = '#{v}'" }
|
229
|
+
seek_conditions = query.join(" AND ")
|
230
|
+
|
231
|
+
where = "WHERE (#{seek_conditions})"
|
232
|
+
|
233
|
+
where += " AND id = '#{id}'" if id
|
234
|
+
order_condition = "ORDER BY #{order}" if order
|
235
|
+
|
236
|
+
sql = "#{base_query} #{where} #{order_condition}"
|
237
|
+
|
238
|
+
rows = Shoden.connection.fetch(sql) || []
|
239
|
+
end
|
240
|
+
|
241
|
+
rows.lazy.map do |row|
|
242
|
+
attrs = row[:data].merge({ id: row[:id] })
|
243
|
+
|
244
|
+
new(attrs)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def attributes
|
249
|
+
sanitized = @attributes.map do |k, _|
|
250
|
+
val = send(k)
|
251
|
+
return if val.nil?
|
252
|
+
[k, val.to_s]
|
253
|
+
end.compact
|
254
|
+
|
255
|
+
Sequel::Postgres::HStore.new(sanitized)
|
256
|
+
end
|
257
|
+
|
155
258
|
private
|
156
259
|
|
260
|
+
def self.base_query
|
261
|
+
"SELECT * FROM \"#{table_name}\""
|
262
|
+
end
|
263
|
+
|
157
264
|
def self.collect(condition = '')
|
158
|
-
|
265
|
+
records = []
|
159
266
|
Shoden.connection.fetch("SELECT * FROM \"#{table_name}\" #{condition}") do |r|
|
160
267
|
attrs = r[:data].merge(id: r[:id])
|
161
|
-
|
268
|
+
records << new(attrs)
|
162
269
|
end
|
163
|
-
|
270
|
+
records
|
164
271
|
end
|
165
272
|
|
166
273
|
def self.table_name
|
@@ -174,53 +281,48 @@ module Shoden
|
|
174
281
|
downcase.to_sym
|
175
282
|
end
|
176
283
|
|
177
|
-
def create_index(name, type = '')
|
284
|
+
def self.create_index(name, type = '')
|
178
285
|
conn.execute <<EOS
|
179
|
-
CREATE #{type.upcase} INDEX index_#{self.
|
286
|
+
CREATE #{type.upcase} INDEX index_#{self.name}_#{name}
|
180
287
|
ON "#{table_name}" (( data -> '#{name}'))
|
181
288
|
WHERE ( data ? '#{name}' );
|
182
289
|
EOS
|
290
|
+
rescue
|
183
291
|
end
|
184
292
|
|
185
|
-
def
|
186
|
-
sanitized = @attributes.map do |k, _|
|
187
|
-
val = send(k)
|
188
|
-
return if val.nil?
|
189
|
-
[k, val.to_s]
|
190
|
-
end.compact
|
191
|
-
|
192
|
-
Sequel::Postgres::HStore.new(sanitized)
|
193
|
-
end
|
194
|
-
|
195
|
-
def lookup(id)
|
196
|
-
raise NotFound if !conn.tables.include?(table_name.to_sym)
|
197
|
-
|
293
|
+
def self.lookup(id)
|
198
294
|
row = table.where(id: id)
|
199
|
-
|
295
|
+
return nil if !row.any?
|
200
296
|
|
201
297
|
row
|
202
298
|
end
|
203
299
|
|
204
|
-
def setup
|
205
|
-
|
206
|
-
Shoden.connection.create_table? table_name do
|
300
|
+
def self.setup
|
301
|
+
conn.create_table? table_name do
|
207
302
|
primary_key :id
|
208
303
|
hstore :data
|
209
304
|
end
|
305
|
+
|
306
|
+
indices.each { |i| create_index(i) }
|
307
|
+
uniques.each { |i| create_index(i, :unique) }
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.destroy_all
|
311
|
+
conn.execute("DELETE FROM \"#{table_name}\"")
|
312
|
+
rescue Sequel::DatabaseError
|
210
313
|
end
|
211
314
|
|
212
|
-
def
|
213
|
-
|
315
|
+
def self.destroy_table
|
316
|
+
conn.drop_table(table_name)
|
317
|
+
rescue Sequel::DatabaseError
|
214
318
|
end
|
215
319
|
|
216
|
-
def table
|
320
|
+
def self.table
|
217
321
|
conn[table_name]
|
218
322
|
end
|
219
323
|
|
220
|
-
def conn
|
221
|
-
|
222
|
-
@created ||= setup
|
223
|
-
c
|
324
|
+
def self.conn
|
325
|
+
Shoden.connection
|
224
326
|
end
|
225
327
|
end
|
226
328
|
end
|
data/shoden.gemspec
CHANGED
data/test/shoden_test.rb
CHANGED
@@ -3,12 +3,35 @@ require 'shoden'
|
|
3
3
|
|
4
4
|
Model = Class.new(Shoden::Model)
|
5
5
|
|
6
|
+
class Person < Shoden::Model
|
7
|
+
attribute :email
|
8
|
+
attribute :origin
|
9
|
+
|
10
|
+
index :origin
|
11
|
+
unique :email
|
12
|
+
end
|
13
|
+
|
14
|
+
class Tree < Shoden::Model
|
15
|
+
attribute :name
|
16
|
+
collection :sprouts, :Sprout
|
17
|
+
end
|
18
|
+
|
19
|
+
class Sprout < Shoden::Model
|
20
|
+
attribute :leaves
|
21
|
+
reference :tree, :Tree
|
22
|
+
end
|
23
|
+
|
6
24
|
class User < Shoden::Model
|
7
25
|
attribute :name
|
8
26
|
end
|
9
27
|
|
28
|
+
class A < Shoden::Model
|
29
|
+
attribute :n, ->(x) { x.to_i }
|
30
|
+
end
|
31
|
+
|
10
32
|
setup do
|
11
|
-
Shoden.
|
33
|
+
Shoden.destroy_tables
|
34
|
+
Shoden.setup
|
12
35
|
end
|
13
36
|
|
14
37
|
test 'model' do
|
@@ -37,17 +60,15 @@ test 'update' do
|
|
37
60
|
assert_equal user.name, 'Cyril'
|
38
61
|
end
|
39
62
|
|
40
|
-
test '
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
63
|
+
test 'filtering' do
|
64
|
+
person = { email: 'elcuervo@elcuervo.net' }
|
65
|
+
Person.create(person)
|
66
|
+
p = Person.filter(person).first
|
45
67
|
|
46
|
-
|
47
|
-
|
48
|
-
reference :tree, :Tree
|
49
|
-
end
|
68
|
+
assert p.email == 'elcuervo@elcuervo.net'
|
69
|
+
end
|
50
70
|
|
71
|
+
test 'relations' do
|
51
72
|
tree = Tree.create(name: 'asd')
|
52
73
|
|
53
74
|
assert tree.id
|
@@ -56,7 +77,14 @@ test 'relations' do
|
|
56
77
|
sprout = tree.sprouts.create(leaves: 4)
|
57
78
|
|
58
79
|
assert sprout.is_a?(Sprout)
|
80
|
+
assert tree.sprouts.all.each.is_a?(Enumerator)
|
81
|
+
|
82
|
+
assert tree.sprouts[sprout.id].id == sprout.id
|
83
|
+
|
84
|
+
assert_equal tree.sprouts.count, 1
|
59
85
|
assert_equal sprout.tree.id, tree.id
|
86
|
+
assert tree.sprouts.first != nil
|
87
|
+
assert tree.sprouts.last != nil
|
60
88
|
end
|
61
89
|
|
62
90
|
test 'deletion' do
|
@@ -65,14 +93,10 @@ test 'deletion' do
|
|
65
93
|
|
66
94
|
user.destroy
|
67
95
|
|
68
|
-
|
96
|
+
assert_equal User[id], nil
|
69
97
|
end
|
70
98
|
|
71
99
|
test 'casting' do
|
72
|
-
class A < Shoden::Model
|
73
|
-
attribute :n, ->(x) { x.to_i }
|
74
|
-
end
|
75
|
-
|
76
100
|
a = A.create(n: 1)
|
77
101
|
a_prime = A[a.id]
|
78
102
|
|
@@ -80,14 +104,6 @@ test 'casting' do
|
|
80
104
|
end
|
81
105
|
|
82
106
|
test 'indices' do
|
83
|
-
class Person < Shoden::Model
|
84
|
-
attribute :email
|
85
|
-
attribute :origin
|
86
|
-
|
87
|
-
index :origin
|
88
|
-
unique :email
|
89
|
-
end
|
90
|
-
|
91
107
|
person = Person.create(email: 'elcuervo@elcuervo.net', origin: 'The internerd')
|
92
108
|
|
93
109
|
assert person.id
|
@@ -98,9 +114,9 @@ test 'indices' do
|
|
98
114
|
end
|
99
115
|
|
100
116
|
test 'basic querying' do
|
117
|
+
User.destroy_all
|
101
118
|
5.times { User.create }
|
102
119
|
|
103
120
|
assert_equal User.all.size, 5
|
104
|
-
|
105
|
-
assert_equal User.last.id, 5
|
121
|
+
|
106
122
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shoden
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- elcuervo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -46,6 +46,7 @@ extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
48
|
- .gems
|
49
|
+
- .travis.yml
|
49
50
|
- HUGS
|
50
51
|
- LICENSE
|
51
52
|
- README.md
|
@@ -80,4 +81,3 @@ specification_version: 4
|
|
80
81
|
summary: Object hash mapper for postgres
|
81
82
|
test_files:
|
82
83
|
- test/shoden_test.rb
|
83
|
-
has_rdoc:
|