em-pg-client-helper 0.1.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.
@@ -0,0 +1,45 @@
1
+ While [`em-pg-client`](https://github.com/royaltm/ruby-em-pg-client) is a
2
+ nice solution to the problem of accessing a PostgreSQL database from within
3
+ [EventMachine](http://rubyeventmachine.com/), it is somewhat... spartan.
4
+ ORMs have spoiled us somewhat, and so the appeal of hand-writing SQL and
5
+ dealing with the finer details of error handling has faded. Hence the
6
+ creation of the `EM::PG::Client::Helper` module. It contains a collection
7
+ of useful helper methods that make it somewhat easier to perform common
8
+ operations against PgSQL databases.
9
+
10
+
11
+ # Installation
12
+
13
+ It's a gem:
14
+
15
+ gem install em-pg-client-helper
16
+
17
+ If you're the sturdy type that likes to run from git:
18
+
19
+ rake build; gem install pkg/em-pg-client-helper-<whatever>.gem
20
+
21
+ Or, if you've eschewed the convenience of Rubygems, then you presumably know
22
+ what to do already.
23
+
24
+
25
+ # Usage
26
+
27
+ To use any of these methods, you will want to add the following require:
28
+
29
+ require 'em-pg-client-helper'
30
+
31
+ Then add this line in any classes you wish to use the helper methods in:
32
+
33
+ include PG::EM::Client::Helper
34
+
35
+ The module documentation for EM::PG::Client::Helper has more information on
36
+ the available methods and how to use them.
37
+
38
+
39
+ # Contributing
40
+
41
+ Bug reports should be sent to the [Github issue
42
+ tracker](https://github.com/mpalmer/em-pg-client-helper/issues), or
43
+ [e-mailed](mailto:theshed+em-pg-client-helper@hezmatt.org). Patches can be
44
+ sent as a Github pull request, or
45
+ [e-mailed](mailto:theshed+em-pg-client-helper@hezmatt.org).
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ task :default => :test
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ Bundler::GemHelper.install_tasks
15
+
16
+ task :release do
17
+ sh "git release"
18
+ end
19
+
20
+ require 'rdoc/task'
21
+
22
+ RDoc::Task.new do |rd|
23
+ rd.main = "README.md"
24
+ rd.title = 'em-pg-client-helper'
25
+ rd.markup = "markdown"
26
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
27
+ end
28
+
29
+ desc "Run guard"
30
+ task :guard do
31
+ require 'guard'
32
+ ::Guard.start(:clear => true)
33
+ while ::Guard.running do
34
+ sleep 0.5
35
+ end
36
+ end
37
+
38
+ require 'rspec/core/rake_task'
39
+ RSpec::Core::RakeTask.new :test do |t|
40
+ t.pattern = "spec/**/*_spec.rb"
41
+ end
@@ -0,0 +1,32 @@
1
+ require 'git-version-bump'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "em-pg-client-helper"
5
+
6
+ s.version = GVB.version
7
+ s.date = GVB.date
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+
11
+ s.homepage = "http://github.com/mpalmer/em-pg-client-helper"
12
+ s.summary = "Simplify common operations using em-pg-client"
13
+ s.authors = ["Matt Palmer"]
14
+
15
+ s.extra_rdoc_files = ["README.md"]
16
+ s.files = `git ls-files`.split("\n")
17
+
18
+ s.add_runtime_dependency "git-version-bump", "~> 0.10"
19
+ s.add_runtime_dependency "em-pg-client", "~> 0.3"
20
+
21
+ s.add_development_dependency 'bundler'
22
+ s.add_development_dependency 'eventmachine'
23
+ s.add_development_dependency 'github-release'
24
+ s.add_development_dependency 'guard-spork'
25
+ s.add_development_dependency 'guard-rspec'
26
+ # Needed for guard
27
+ s.add_development_dependency 'rb-inotify', '~> 0.9'
28
+ s.add_development_dependency 'pry-debugger'
29
+ s.add_development_dependency 'rake'
30
+ s.add_development_dependency 'rdoc'
31
+ s.add_development_dependency 'rspec'
32
+ end
@@ -0,0 +1,148 @@
1
+ require 'pg/em'
2
+ require 'pg/em/connection_pool'
3
+
4
+ # Some helper methods to make working with em-pg-client slightly less like
5
+ # trying to build a house with a packet of seeds and a particle
6
+ # accelerator... backwards.
7
+ #
8
+ module PG::EM::Client::Helper
9
+ # Generate SQL for an insert statement into `tbl`, with the fields and
10
+ # data given by the keys and values, respectively, of `params`. Returns
11
+ # a two-element array consisting of the parameterised SQL as the first
12
+ # element, and the array of parameters as the second element.
13
+ #
14
+ def insert_sql(tbl, params)
15
+ keys = params.keys.map { |k| quote_identifier(k.to_s) }.join(',')
16
+ vals = params.values
17
+ val_places = (1..vals.length).to_a.map { |i| "$#{i}" }.join(',')
18
+
19
+ ["INSERT INTO #{quote_identifier(tbl)} (#{keys}) VALUES (#{val_places})", vals]
20
+ end
21
+
22
+ # Run an insert query, without having to write a great pile of SQL all by
23
+ # yourself.
24
+ #
25
+ # Arguments:
26
+ #
27
+ # * `db` -- A PG::EM::Client or PG::EM::ConnectionPool instance, against
28
+ # which all database operations will be executed.
29
+ #
30
+ # * `tbl` -- The name of the table into which you wish to insert your data.
31
+ # This parameter will be automatically quoted, if necessary.
32
+ #
33
+ # * `params` -- A hash containing the fields you wish to insert into
34
+ # (the keys of the hash) and the values to insert into each field (the
35
+ # values of the hash). All field names and data will be automatically
36
+ # quoted and made safe, so you're automatically SQL injection-proof!
37
+ #
38
+ # This method returns the deferrable in which the query is being called;
39
+ # this means you should attach the code to run after the query completes
40
+ # with `#callback`, and you can attach an error handler with `#errback`
41
+ # if you like.
42
+ #
43
+ def db_insert(db, tbl, params)
44
+ db.exec_defer(*insert_sql(tbl, params))
45
+ end
46
+
47
+ # Execute code in a transaction.
48
+ #
49
+ # Arguments:
50
+ #
51
+ # * `db` -- A PG::EM::Client or PG::EM::ConnectionPool instance, against
52
+ # which all database operations will be executed. If you pass a
53
+ # ConnectionPool, we will automatically hold a single connection for
54
+ # the transaction to complete against, so you don't have to worry
55
+ # about that, either.
56
+ #
57
+ # * `blk` -- A block of code which will be executed within the context
58
+ # of the transaction. This block will be passed a
59
+ # `PG::EM::Client::Helper::Transaction` instance, which has methods to
60
+ # allow you to commit or rollback the transaction, and execute SQL
61
+ # statements within the context of the transaction.
62
+ #
63
+ # Returns a deferrable object, on which you can call `#callback` and
64
+ # `#errback` to define what to do when the transaction succeeds or fails,
65
+ # respectively.
66
+ #
67
+ def db_transaction(db, opts = {}, &blk)
68
+ if db.is_a? PG::EM::ConnectionPool
69
+ db.__send__(:hold_deferrable) do |conn|
70
+ ::PG::EM::Client::Helper::Transaction.new(conn, opts, &blk)
71
+ end
72
+ else
73
+ ::PG::EM::Client::Helper::Transaction.new(db, opts, &blk)
74
+ end
75
+ end
76
+
77
+ # Take a PgSQL identifier (anything that isn't data, basically) and quote
78
+ # it so that it will always be valid, no matter what insanity someone's
79
+ # decided to put in their names.
80
+ #
81
+ def quote_identifier(id)
82
+ '"' + id.gsub(/"/, '""') + '"'
83
+ end
84
+
85
+ class Transaction
86
+ include ::PG::EM::Client::Helper
87
+ include ::EventMachine::Deferrable
88
+
89
+ # Create a new transaction. You shouldn't have to call this yourself;
90
+ # `db_transaction` should create one and pass it to your block.
91
+ def initialize(conn, opts, &blk)
92
+ @conn = conn
93
+ @opts = opts
94
+ @active = true
95
+
96
+ conn.exec_defer("BEGIN").callback do
97
+ blk.call(self)
98
+ commit if @active
99
+ end.errback { |ex| rollback(ex) }
100
+ end
101
+
102
+ # Signal the database to commit this transaction. You must do this
103
+ # once you've completed your queries, it won't be called automatically
104
+ # for you. Once you've committed the transaction, you cannot use it
105
+ # again; if you execute a query against a committed transaction, an
106
+ # exception will be raised.
107
+ #
108
+ def commit
109
+ @conn.exec_defer("COMMIT").callback do
110
+ self.succeed
111
+ @active = false
112
+ end.errback { |ex| rollback(ex) }
113
+ end
114
+
115
+ # Signal the database to abort this transaction. You only need to
116
+ # call this method if you need to rollback for some business logic
117
+ # reason -- a rollback will be automatically performed for you in the
118
+ # event of a database error or other exception.
119
+ #
120
+ def rollback(ex)
121
+ @conn.exec_defer("ROLLBACK") do
122
+ @active = false
123
+ self.fail(ex)
124
+ end
125
+ end
126
+
127
+ # Insert a row of data into the database table `tbl`, using the keys
128
+ # from the `params` hash as the field names, and the values from the
129
+ # `params` hash as the field data. Once the query has completed,
130
+ # `blk` will be called to allow the transaction to continue.
131
+ #
132
+ def insert(tbl, params, &blk)
133
+ exec(*insert_sql(tbl, params), &blk)
134
+ end
135
+
136
+ # Execute an arbitrary block of SQL in `sql` within the transaction.
137
+ # If you need to pass dynamic values to the query, those should be
138
+ # given in `values`, and referenced in `sql` as `$1`, `$2`, etc. The
139
+ # given block will be called if and when the query completes
140
+ # successfully.
141
+ #
142
+ def exec(sql, values=[], &blk)
143
+ df = @conn.exec_defer(sql, values).
144
+ errback { |ex| rollback(ex) }
145
+ df.callback(&blk) if blk
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,14 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe "PG::EM::Client::Helper#db_insert" do
4
+ it "Calls the right SQL" do
5
+ expect(db = double).
6
+ to receive(:exec_defer).
7
+ with(
8
+ 'INSERT INTO "foo" ("bar") VALUES ($1)',
9
+ ['baz']
10
+ )
11
+
12
+ db_insert(db, "foo", :bar => "baz")
13
+ end
14
+ end
@@ -0,0 +1,76 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe "PG::EM::Client::Helper#db_transaction" do
4
+ def mock_query_chain(queries, fail_on = -1, &blk)
5
+ mock_conn = double(PG::EM::Client)
6
+
7
+ queries.each_with_index do |q, i|
8
+ df = EM::DefaultDeferrable.new
9
+
10
+ ex = expect(mock_conn).
11
+ to receive(:exec_defer).
12
+ with(*q).
13
+ and_return(df)
14
+
15
+ # Rollback expects a yield
16
+ if q == ["ROLLBACK"]
17
+ ex.and_yield()
18
+ end
19
+
20
+ if i == fail_on
21
+ df.fail
22
+ else
23
+ df.succeed
24
+ end
25
+ end
26
+
27
+ EM.run_block do
28
+ db_transaction(mock_conn, &blk)
29
+ end
30
+ end
31
+
32
+ it "runs a BEGIN/COMMIT cycle by default" do
33
+ mock_query_chain([["BEGIN"], ["COMMIT"]]) do
34
+ # Nothing
35
+ end
36
+ end
37
+
38
+ it "rolls back if BEGIN fails" do
39
+ mock_query_chain([["BEGIN"], ["ROLLBACK"]], 0) do
40
+ # Nothing
41
+ end
42
+ end
43
+
44
+ it "rolls back if COMMIT fails" do
45
+ mock_query_chain([["BEGIN"], ["COMMIT"], ["ROLLBACK"]], 1) do
46
+ # Nothing
47
+ end
48
+ end
49
+
50
+ it "runs a simple INSERT" do
51
+ mock_query_chain([
52
+ ["BEGIN"],
53
+ ['INSERT INTO "foo" ("bar") VALUES ($1)',
54
+ ["baz"]
55
+ ],
56
+ ["COMMIT"]
57
+ ]) do |txn|
58
+ txn.insert("foo", :bar => 'baz')
59
+ end
60
+ end
61
+
62
+ it "rolls back after a failed INSERT" do
63
+ mock_query_chain(
64
+ [
65
+ ["BEGIN"],
66
+ ['INSERT INTO "foo" ("bar") VALUES ($1)',
67
+ ["baz"]
68
+ ],
69
+ ["ROLLBACK"]
70
+ ],
71
+ 1
72
+ ) do |txn|
73
+ txn.insert("foo", :bar => 'baz')
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe "PG::EM::Client::Helper#insert_sql" do
4
+ it "assembles a simple query correctly" do
5
+ expect(insert_sql("foo", :bar => "baz", :wombat => 42)).
6
+ to eq([
7
+ 'INSERT INTO "foo" ("bar","wombat") VALUES ($1,$2)',
8
+ ["baz", 42]
9
+ ])
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ require 'spork'
2
+
3
+ Spork.prefork do
4
+ require 'bundler'
5
+ Bundler.setup(:default, :test)
6
+ require 'rspec/core'
7
+ require 'rspec/mocks'
8
+
9
+ require 'pry'
10
+
11
+ RSpec.configure do |config|
12
+ config.fail_fast = true
13
+ # config.full_backtrace = true
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+ end
19
+ end
20
+
21
+ Spork.each_run do
22
+ require 'em-pg-client-helper'
23
+
24
+ RSpec.configure do |config|
25
+ config.include ::PG::EM::Client::Helper
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,255 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-pg-client-helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Palmer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: git-version-bump
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.10'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.10'
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-pg-client
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.3'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: eventmachine
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: github-release
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: guard-spork
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: guard-rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rb-inotify
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: '0.9'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.9'
142
+ - !ruby/object:Gem::Dependency
143
+ name: pry-debugger
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: rake
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: rdoc
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: rspec
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ description:
207
+ email:
208
+ executables: []
209
+ extensions: []
210
+ extra_rdoc_files:
211
+ - README.md
212
+ files:
213
+ - .gitignore
214
+ - Gemfile
215
+ - Guardfile
216
+ - LICENCE
217
+ - README.md
218
+ - Rakefile
219
+ - em-pg-client-helper.gemspec
220
+ - lib/em-pg-client-helper.rb
221
+ - spec/db_insert_spec.rb
222
+ - spec/db_transaction_spec.rb
223
+ - spec/insert_sql_spec.rb
224
+ - spec/spec_helper.rb
225
+ homepage: http://github.com/mpalmer/em-pg-client-helper
226
+ licenses: []
227
+ post_install_message:
228
+ rdoc_options: []
229
+ require_paths:
230
+ - lib
231
+ required_ruby_version: !ruby/object:Gem::Requirement
232
+ none: false
233
+ requirements:
234
+ - - ! '>='
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ segments:
238
+ - 0
239
+ hash: -3158041732847442492
240
+ required_rubygems_version: !ruby/object:Gem::Requirement
241
+ none: false
242
+ requirements:
243
+ - - ! '>='
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ segments:
247
+ - 0
248
+ hash: -3158041732847442492
249
+ requirements: []
250
+ rubyforge_project:
251
+ rubygems_version: 1.8.23
252
+ signing_key:
253
+ specification_version: 3
254
+ summary: Simplify common operations using em-pg-client
255
+ test_files: []