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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7c861e4cb7f7edd9609864081c18f45913c8a82
4
- data.tar.gz: af092fb9fb7046d81bc703d90900e9f2bb83905f
3
+ metadata.gz: 28a3009afa5b70167a8c9128a356002e11224695
4
+ data.tar.gz: 77efc3fd3a51f5d98ad27591657e96e56f7b9628
5
5
  SHA512:
6
- metadata.gz: faef768a2f83007e65b9bbaa5ae81414166175b55fac9de499faff85445af98390409572a608f7f54a13b63d9feeeb5933599b7c263aafc4ecd345237c8ab21a
7
- data.tar.gz: a24ed94a720330c281f39a88df8ba7868a72438818acb3ecafd6b97984f2afcb96a419ecb4881cc68071e990256569a8dc7e6f0f16bf67cc26678c673398de91
6
+ metadata.gz: 0ae31056c6817127730fdebeddd82ecf8cbff424c9dbedef97dd1674825a421c4b9725670ddaf5094828703fdb0214de600011b718aa5bdbfa4bb0c7dc837853
7
+ data.tar.gz: 72d6ca3fa679e24c8201ba4231228f56f6a59c6b4f399b8ec70b9566b9eda835c00750759402ceb126d684b87095d48a28d0c811422b06c7c97b6b06f9bb2e74
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+
7
+ services: postgresql
8
+
9
+ before_script:
10
+ - psql -c 'create database shoden_test;' -U postgres
11
+
12
+ env:
13
+ global:
14
+ - DATABASE_URL=postgres://localhost:5432/shoden_test
@@ -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 = "#{parent.class.to_reference}_id"
14
- klass.create(args.merge(
15
- key => parent.id
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.connection
29
- @_connection ||= Sequel.connect(url)
64
+ def self.models
65
+ @_models ||= Set.new
30
66
  end
31
67
 
32
- def self.destroy_all
33
- connection.tables.select do |t|
34
- connection.drop_table(t) if t.to_s.start_with?('Shoden::')
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
- raise MissingID if !defined?(@_id)
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
- if defined? @_id
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
- models = []
265
+ records = []
159
266
  Shoden.connection.fetch("SELECT * FROM \"#{table_name}\" #{condition}") do |r|
160
267
  attrs = r[:data].merge(id: r[:id])
161
- models << new(attrs)
268
+ records << new(attrs)
162
269
  end
163
- models
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.class.name}_#{name}
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 sanitized_attrs
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
- raise NotFound if !row.any?
295
+ return nil if !row.any?
200
296
 
201
297
  row
202
298
  end
203
299
 
204
- def setup
205
- Shoden.connection.execute("CREATE EXTENSION IF NOT EXISTS hstore")
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 table_name
213
- self.class.table_name
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
- c = Shoden.connection
222
- @created ||= setup
223
- c
324
+ def self.conn
325
+ Shoden.connection
224
326
  end
225
327
  end
226
328
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "shoden"
3
- s.version = "0.3.0"
3
+ s.version = "0.4.0"
4
4
  s.summary = "Object hash mapper for postgres"
5
5
  s.description = "Slim postgres models"
6
6
  s.authors = ["elcuervo"]
@@ -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.destroy_all
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 'relations' do
41
- class Tree < Shoden::Model
42
- attribute :name
43
- collection :sprouts, :Sprout
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
- class Sprout < Shoden::Model
47
- attribute :leaves
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
- assert_raise(Shoden::NotFound) { User[id] }
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
- assert_equal User.first.id, 1
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.3.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-10-30 00:00:00.000000000 Z
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: