shoden 0.3.0 → 0.4.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
  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: