perpetuity-postgres 0.0.4 → 0.0.5

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: 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