perpetuity-postgres 0.0.4 → 0.0.5

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: 84195d183dd75f856a69ea888e133407ab33596c
4
- data.tar.gz: a08c32b762b10d9d33b26b099703b6521af12930
3
+ metadata.gz: 558b498d177b994d2d6ff89101aba27d040a4fdf
4
+ data.tar.gz: e69cfe6e5454170c6565c7018eb002f5d2e3e161
5
5
  SHA512:
6
- metadata.gz: 58737d3a8d9f9a7e462037189df2d8c500367f59b7f2d5505d22cb25bc2060f7557e6208bf4ef79f392660e842989c2c97a2ac49456885a4c7dce113458d41ec
7
- data.tar.gz: c39806552ea5979a1902f0a9eaec465e5d65f4bfa0e0690aee06fcf5f84572c3a695d8b18d06d53235cf13616d2b007576d80eb002bb9ea08496eb6df491ee0c
6
+ metadata.gz: 39653126b7754933a22d9ba8fd8dac82436c71340c298954245783a84bd1868c6a566937ead0d5379d300fd98becd7c773c9f143d1db43f9bcf9e8879817647d
7
+ data.tar.gz: 4bb4d6b025ed091a7b975b379d45bd7514215960771fc1b2f3a5ac4fc9cbd03cfa63e264057d36ea7d64f944ec61cc066dc3c10b798ac2d1d81533c4db7fe821
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx
5
+ - 2.0.0
6
+ - 2.1.1
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: rbx
10
+ addons:
11
+ postgresql: 9.3
12
+ before_script:
13
+ - psql -c 'create database perpetuity_gem_test;' -U postgres
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## Version 0.0.5
2
+
3
+ - Name indexes explicitly based on table and attributes. Previously we were counting on the PostgreSQL naming convention, but that could break in future versions of Postgres.
4
+ - Fixed a bug that was causing duplicate indexes to be built
5
+ - Deserialize booleans. They should be declared in the mapper as `TrueClass` or `FalseClass`.
6
+ - Fixed a bug that choked when you tried to save an attribute for an object if that column was not yet added.
7
+ - Allow deleting an array of objects
8
+ - Removed VARCHAR conversion. VARCHAR and TEXT are stored exactly the same in PostgreSQL. There's no reason to limit yourself to a VARCHAR unless you're using table constraints, which are far more flexible.
9
+
1
10
  ## Version 0.0.4
2
11
 
3
12
  - Set minimum logging from the database to warnings. This silences info messages like those from `DROP TABLE IF NOT EXISTS ...` when the table doesn't exist.
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -49,7 +49,7 @@ module Perpetuity
49
49
  end
50
50
 
51
51
  def table
52
- name.gsub("_#{attributes.map(&:to_s).join('_')}_idx", '')
52
+ name.gsub("_#{attributes.map(&:to_s).join('_')}_index", '')
53
53
  end
54
54
 
55
55
  def == other
@@ -62,6 +62,10 @@ module Perpetuity
62
62
  def eql? other
63
63
  self == other
64
64
  end
65
+
66
+ def hash
67
+ name.to_s.hash
68
+ end
65
69
  end
66
70
  end
67
71
  end
@@ -15,6 +15,11 @@ module Perpetuity
15
15
  "(#{column_names.join(',')}) VALUES #{value_strings}"
16
16
  end
17
17
 
18
+ def [] key
19
+ index = column_names.index(key.to_s)
20
+ values.first[index]
21
+ end
22
+
18
23
  def []= column, value
19
24
  value = TextValue.new(value)
20
25
  if column_names.include? column
@@ -63,6 +63,8 @@ module Perpetuity
63
63
  value = value.to_i
64
64
  elsif attribute.type == Time
65
65
  value = TimestampValue.from_sql(value).to_time
66
+ elsif [TrueClass, FalseClass].include? attribute.type
67
+ value = value.downcase.start_with? 't'
66
68
  end
67
69
  end
68
70
 
@@ -9,6 +9,19 @@ module Perpetuity
9
9
  NoDefaultValue = Module.new
10
10
  UUID = Module.new
11
11
 
12
+ SQL_TYPE_MAP = {
13
+ String => 'TEXT',
14
+ Integer => 'BIGINT',
15
+ Fixnum => 'BIGINT',
16
+ Bignum => 'NUMERIC',
17
+ BigDecimal => 'NUMERIC',
18
+ Float => 'FLOAT',
19
+ UUID => 'UUID',
20
+ Time => 'TIMESTAMPTZ',
21
+ TrueClass => 'BOOLEAN',
22
+ FalseClass => 'BOOLEAN'
23
+ }.tap{|m| m.default = 'JSON' }
24
+
12
25
  def initialize name, type, options={}
13
26
  @name = name
14
27
  @type = type
@@ -18,25 +31,7 @@ module Perpetuity
18
31
  end
19
32
 
20
33
  def sql_type
21
- if type == String
22
- if max_length
23
- "VARCHAR(#{max_length})"
24
- else
25
- 'TEXT'
26
- end
27
- elsif type == Integer or type == Fixnum
28
- 'BIGINT'
29
- elsif type == Bignum or type == BigDecimal
30
- 'NUMERIC'
31
- elsif type == Float
32
- 'FLOAT'
33
- elsif type == UUID
34
- 'UUID'
35
- elsif type == Time
36
- 'TIMESTAMPTZ'
37
- else
38
- 'JSON'
39
- end
34
+ SQL_TYPE_MAP[type]
40
35
  end
41
36
 
42
37
  def sql_declaration
@@ -1,5 +1,5 @@
1
1
  module Perpetuity
2
2
  class Postgres
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -70,11 +70,23 @@ module Perpetuity
70
70
  raise
71
71
  end
72
72
 
73
- def delete id, klass
73
+ def delete ids, klass
74
+ ids = Array(ids)
74
75
  table = TableName.new(klass)
75
- id_string = TextValue.new(id)
76
- sql = "DELETE FROM #{table} WHERE id = #{id_string}"
77
- connection.execute(sql).to_a
76
+
77
+ if ids.one?
78
+ id_string = TextValue.new(ids.first)
79
+ sql = "DELETE FROM #{table} WHERE id = #{id_string}"
80
+
81
+ connection.execute(sql).to_a
82
+ elsif ids.none?
83
+ # Do nothing, we weren't given anything to delete
84
+ else
85
+ id_string = ids.map { |id| TextValue.new(id) }
86
+ sql = "DELETE FROM #{table} WHERE id IN (#{id_string.join(',')})"
87
+
88
+ connection.execute(sql).to_a
89
+ end
78
90
  end
79
91
 
80
92
  def count klass, query='TRUE', options={}, &block
@@ -128,10 +140,19 @@ module Perpetuity
128
140
  def update klass, id, attributes
129
141
  sql = SQLUpdate.new(klass, id, attributes).to_s
130
142
  connection.execute(sql).to_a
143
+ rescue PG::UndefinedColumn => e
144
+ if e.message =~ /column "(.*)" of relation "(.*)" does not exist/
145
+ column_name = $1
146
+ table_name = $2
147
+ add_column table_name, column_name, [Attribute.new(column_name, attributes[column_name].class)]
148
+ retry
149
+ else
150
+ raise e
151
+ end
131
152
  end
132
153
 
133
154
  def index klass, attributes, options={}
134
- name = "#{klass}_#{Array(attributes).map(&:name).join('_')}_idx"
155
+ name = "#{klass}_#{Array(attributes).map(&:name).join('_')}_index"
135
156
  index = Index.new(name: name,
136
157
  attributes: Array(attributes),
137
158
  unique: !!options[:unique],
@@ -142,13 +163,18 @@ module Perpetuity
142
163
 
143
164
  def indexes klass
144
165
  @indexes ||= {}
145
- @indexes[klass] ||= IndexCollection.new(klass)
166
+ @indexes[klass] ||= active_indexes(klass)
146
167
  end
147
168
 
148
169
  def activate_index! index
170
+ return if index.active?
171
+
172
+ attribute_names = index.attribute_names.join('_')
173
+ index_name = "#{index.table}_#{attribute_names}_index"
149
174
  sql = "CREATE "
150
175
  sql << "UNIQUE " if index.unique?
151
- sql << "INDEX ON #{TableName.new(index.table)} (#{index.attribute_names.join(',')})"
176
+ sql << "INDEX \"#{index_name}\" "
177
+ sql << "ON #{TableName.new(index.table)} (#{attribute_names})"
152
178
  connection.execute(sql)
153
179
  index.activate!
154
180
  rescue PG::UndefinedTable => e
@@ -168,7 +194,7 @@ module Perpetuity
168
194
  pg_index.indisready AS active
169
195
  FROM pg_class
170
196
  INNER JOIN pg_index ON pg_class.oid = pg_index.indexrelid
171
- WHERE pg_class.relname ~ '^#{table}.*idx$'
197
+ WHERE pg_class.relname ~ '^#{table}.*index$'
172
198
  SQL
173
199
 
174
200
  indexes = connection.execute(sql).map do |index|
@@ -5,7 +5,7 @@ module Perpetuity
5
5
  describe Index do
6
6
  it 'can be generated from SQL results' do
7
7
  index_hash = {
8
- "name"=>"Object_id_name_idx",
8
+ "name"=>"Object_id_name_index",
9
9
  "attributes"=>"{id,name}",
10
10
  "unique"=>"t",
11
11
  "active"=>"t"
@@ -13,7 +13,7 @@ module Perpetuity
13
13
 
14
14
  index = Index.from_sql(index_hash)
15
15
  index.attribute_names.should == ['id', 'name']
16
- index.name.should == 'Object_id_name_idx'
16
+ index.name.should == 'Object_id_name_index'
17
17
  index.table.should == 'Object'
18
18
  index.should be_unique
19
19
  index.should be_active
@@ -47,7 +47,7 @@ module Perpetuity
47
47
  it 'is not equal to an index with different attributes' do
48
48
  new_index = Index.new(attributes: [:lol],
49
49
  name: name,
50
- unique: false)
50
+ unique: unique)
51
51
 
52
52
  new_index.should_not == index
53
53
  end
@@ -55,6 +55,11 @@ module Perpetuity
55
55
  [['name', "'Jamie'"], ['age', 31]]
56
56
  end
57
57
 
58
+ it 'accesses values like a hash' do
59
+ serialized['age'].should == 31
60
+ serialized[:age].should == 31
61
+ end
62
+
58
63
  it 'equals another with the same data' do
59
64
  original = SerializedData.new([:a, :b], [1, 2])
60
65
  duplicate = SerializedData.new([:a, :b], [1, 2])
@@ -125,12 +125,13 @@ module Perpetuity
125
125
 
126
126
  let(:article_class) do
127
127
  Class.new do
128
- attr_reader :title, :body, :views, :published_at
128
+ attr_reader :title, :body, :views, :published_at, :published
129
129
  def initialize attributes={}
130
130
  @title = attributes[:title]
131
131
  @body = attributes[:body]
132
132
  @views = attributes.fetch(:views) { 0 }
133
133
  @published_at = attributes.fetch(:published_at) { Time.now }
134
+ @published = attributes.fetch(:published) { false }
134
135
  end
135
136
 
136
137
  def == other
@@ -138,7 +139,8 @@ module Perpetuity
138
139
  other.title == title &&
139
140
  other.body == body &&
140
141
  other.views == views &&
141
- other.published_at == published_at
142
+ other.published_at == published_at &&
143
+ other.published == published
142
144
  end
143
145
  end
144
146
  end
@@ -151,6 +153,7 @@ module Perpetuity
151
153
  attribute :body, type: String
152
154
  attribute :views, type: Integer
153
155
  attribute :published_at, type: Time
156
+ attribute :published, type: TrueClass
154
157
  end.new(registry)
155
158
  end
156
159
 
@@ -161,14 +164,16 @@ module Perpetuity
161
164
  'title' => 'Title',
162
165
  'body' => 'Body',
163
166
  'views' => '0',
164
- 'published_at' => '2013-01-02 03:04:05.123456-05'
167
+ 'published_at' => '2013-01-02 03:04:05.123456-05',
168
+ 'published' => 't'
165
169
  }
166
170
 
167
171
  article = article_class.new(
168
172
  title: 'Title',
169
173
  body: 'Body',
170
174
  views: 0,
171
- published_at: Time.new(2013, 1, 2, 3, 4, 5.123456, '-05:00')
175
+ published_at: Time.new(2013, 1, 2, 3, 4, 5.123456, '-05:00'),
176
+ published: true
172
177
  )
173
178
  serializer.unserialize(serialized_article).should == article
174
179
  end
@@ -5,7 +5,7 @@ module Perpetuity
5
5
  class Postgres
6
6
  class Table
7
7
  describe Attribute do
8
- let(:title) { Attribute.new('title', String, max_length: 40) }
8
+ let(:title) { Attribute.new('title', String) }
9
9
 
10
10
  it 'knows its name' do
11
11
  title.name.should == 'title'
@@ -44,7 +44,6 @@ module Perpetuity
44
44
  let(:body) { Attribute.new('body', String, default: 'foo') }
45
45
 
46
46
  it 'converts to the proper SQL type' do
47
- title.sql_type.should == 'VARCHAR(40)'
48
47
  body.sql_type.should == 'TEXT'
49
48
  end
50
49
 
@@ -81,6 +80,13 @@ module Perpetuity
81
80
  end
82
81
  end
83
82
 
83
+ describe 'booleans' do
84
+ it 'is stored in a BOOLEAN column' do
85
+ Attribute.new(:true, TrueClass).sql_type.should == 'BOOLEAN'
86
+ Attribute.new(:false, FalseClass).sql_type.should == 'BOOLEAN'
87
+ end
88
+ end
89
+
84
90
  describe 'non-serializable types' do
85
91
  let(:author) { Attribute.new('author', Object) }
86
92
 
@@ -26,7 +26,7 @@ module Perpetuity
26
26
 
27
27
  it 'generates proper SQL to create itself' do
28
28
  table.create_table_sql.should ==
29
- 'CREATE TABLE IF NOT EXISTS "Article" (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), title VARCHAR(40), body TEXT, author JSON, published_at TIMESTAMPTZ, views BIGINT)'
29
+ 'CREATE TABLE IF NOT EXISTS "Article" (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), title TEXT, body TEXT, author JSON, published_at TIMESTAMPTZ, views BIGINT)'
30
30
  end
31
31
 
32
32
  describe 'id column' do
@@ -141,12 +141,6 @@ module Perpetuity
141
141
  postgres.retrieve('Article', 'TRUE').should == []
142
142
  end
143
143
 
144
- it 'deletes all records' do
145
- postgres.insert 'User', data, attributes
146
- postgres.delete_all 'User'
147
- postgres.count('User').should == 0
148
- end
149
-
150
144
  it 'updates a specific record' do
151
145
  id = postgres.insert('User', data, attributes).first
152
146
  postgres.update 'User', id, name: 'foo'
@@ -155,9 +149,30 @@ module Perpetuity
155
149
  retrieved.first['name'].should == 'foo'
156
150
  end
157
151
 
158
- it 'deletes a record with a specific id' do
152
+ it 'updates a record when a column does not currently exist' do
159
153
  id = postgres.insert('User', data, attributes).first
160
- expect { postgres.delete id, 'User' }.to change { postgres.count 'User' }.by -1
154
+ postgres.update 'User', id, Postgres::SerializedData.new(['foo'], ["'bar'"])
155
+
156
+ retrieved = postgres.retrieve('User', "id = '#{id}'")
157
+ retrieved.first['foo'].should == 'bar'
158
+ end
159
+
160
+ describe 'deletion' do
161
+ it 'deletes all records' do
162
+ postgres.insert 'User', data, attributes
163
+ postgres.delete_all 'User'
164
+ postgres.count('User').should == 0
165
+ end
166
+
167
+ it 'deletes a record with a specific id' do
168
+ id = postgres.insert('User', data, attributes).first
169
+ expect { postgres.delete id, 'User' }.to change { postgres.count 'User' }.by -1
170
+ end
171
+
172
+ it 'deletes records with specific ids' do
173
+ ids = Array.new(3) { postgres.insert('User', data, attributes).first }
174
+ expect { postgres.delete ids.take(2), 'User' }.to change { postgres.count 'User' }.by -2
175
+ end
161
176
  end
162
177
 
163
178
  describe 'incrementing/decrementing' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perpetuity-postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Gaskins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-02 00:00:00.000000000 Z
11
+ date: 2014-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -88,6 +88,7 @@ extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
90
  - .gitignore
91
+ - .travis.yml
91
92
  - CHANGELOG.md
92
93
  - Gemfile
93
94
  - LICENSE.txt
@@ -178,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
179
  version: '0'
179
180
  requirements: []
180
181
  rubyforge_project:
181
- rubygems_version: 2.0.3
182
+ rubygems_version: 2.2.2
182
183
  signing_key:
183
184
  specification_version: 4
184
185
  summary: PostgreSQL adapter for Perpetuity