uses-store-procedures 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.
- data/MIT-LICENSE +20 -0
- data/README.md +133 -0
- data/Rakefile +29 -0
- data/lib/tasks/uses-stored-procedures_tasks.rake +4 -0
- data/lib/uses_stored_procedures.rb +107 -0
- data/lib/uses_stored_procedures/hash_with_attributes.rb +68 -0
- data/lib/uses_stored_procedures/railtie.rb +10 -0
- data/lib/uses_stored_procedures/version.rb +3 -0
- metadata +75 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Leopold O'Donnell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
[](https://travis-ci.org/leopoldodonnell/uses-stored-procedures)
|
2
|
+
[](https://gemnasium.com/leopoldodonnell/uses-stored-procedures)
|
3
|
+
[](https://codeclimate.com/github/leopoldodonnell/uses-stored-procedures)
|
4
|
+
|
5
|
+
# UsesStoredProcedures
|
6
|
+
|
7
|
+
Extends ActiveRecord with the ability to use SQL Stored Procedures in Rails.
|
8
|
+
|
9
|
+
So, you really need to use stored procedures in your Rails application. This gem extends ActiveRecord with a class method to add to your models or services. Sure stored procedures are *not the Rails way* they are an occasional project necessity. Your reasons may stem from a need to integrate with a legacy database with built-in business rules, or a need to improve performance where other avenues have been exhausted (given available project time). If you need this, *you know who you are*, I don't need to tell you why.
|
10
|
+
|
11
|
+
## Version
|
12
|
+
|
13
|
+
0.1.0
|
14
|
+
|
15
|
+
## Requirements
|
16
|
+
|
17
|
+
### Ruby
|
18
|
+
|
19
|
+
- 1.8.7, 1.9
|
20
|
+
|
21
|
+
### Rails
|
22
|
+
|
23
|
+
- 3.1.x or 3.2.x
|
24
|
+
|
25
|
+
### ORM
|
26
|
+
|
27
|
+
- ActiveRecord
|
28
|
+
- Mysql2, MySQL (Tested/Passing)
|
29
|
+
- SQLServer (Untested, but should work)
|
30
|
+
- PostgreSQL (Untested, unsure - please try it and notify the author)
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
The current state of this gem is tested with MySQL, but it also does detection of SQLServer and PostgreSQL which will
|
35
|
+
be tested when time permits. Please contact the author if you've gotten it to work before this happens.
|
36
|
+
|
37
|
+
In your Gemfile
|
38
|
+
|
39
|
+
gem 'uses-stored-procedures', git => "git://github.com/leopoldodonnell/uses-stored-procedures"
|
40
|
+
|
41
|
+
Then from your project root:
|
42
|
+
|
43
|
+
> bundle
|
44
|
+
|
45
|
+
|
46
|
+
## Using uses_stored_procedures to call stored procedures from Rails
|
47
|
+
|
48
|
+
**UsesStoredProcedues** extends ActiveRecord::Base with the single class method that takes the name of the procedure as a symbol, and optional parameters to generate a class and instance method that can be called to run the stored procedure.
|
49
|
+
|
50
|
+
Here's a simple example:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class MyClass < ActiveRecord::Base
|
54
|
+
use_stored_proc :get_office_by_country {|row| "#{row.country} #{row.city}, phone: #{row.phone}"}
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
Which generates:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
def self.get_office_by_country(*args, &block)
|
62
|
+
# code
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_office_by_country(*args, &block)
|
66
|
+
self.class.get_offic_by_country(args, block)
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
So later you can do the following:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
result = MyClass.get_office_by_country 'Canada'
|
74
|
+
result.each {|item| puts item}
|
75
|
+
```
|
76
|
+
|
77
|
+
Note that the object passed to the block is a *UsesStoredProcedures#HashWithAttributes* instance that provides accessors for all of its keys.
|
78
|
+
|
79
|
+
### Syntax
|
80
|
+
|
81
|
+
The method signature for *use_stored_proc* is:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
def self.use_stored_proc(method_name, *options, &block)
|
85
|
+
```
|
86
|
+
|
87
|
+
That then generates a class and instance method named *method_name*.
|
88
|
+
|
89
|
+
* method_name - is the symbolic name of the stored procedure and is the name given to the generated class and instance methods. By default, the stored procedure that is called will be a stringified version of this method_name. Use the :proc_name option if the method_name won't be matching actual stored procedure name.
|
90
|
+
|
91
|
+
* options - is an array of arguments that are provided as *:arg_name => value* and where the last argument may be a block, or method name.
|
92
|
+
|
93
|
+
####Options
|
94
|
+
|
95
|
+
* proc_name - if the stored procedure name is different than method_name parameter, provide a string that matches the actual stored procedure name.
|
96
|
+
|
97
|
+
* filter - the name of a class method that receives an instance of *UsesStoredProcedures#HashWithAttributes* to map returned rows.
|
98
|
+
|
99
|
+
* block - make the last parameter a block that receive an instance of *UsesStoredProcedures#HashWithAttributes* to map returned rows.
|
100
|
+
|
101
|
+
#### Generated Methods
|
102
|
+
|
103
|
+
The generated methods all take a variable number of parameters that are mapped to the stored procedure call and can also take a block that can be used to map the returned rows. By default the returned value will be an array of values that are either instances of *UsesStoredProcedures#HashWithAttributes*, or mapped by the block. Note that passing a block overrides the block or method provided when calling *#use_stored_proc*
|
104
|
+
|
105
|
+
### Another Example
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
require 'uses_stored_procedures'
|
109
|
+
|
110
|
+
class ClientServices
|
111
|
+
include UsesStoredProcedures
|
112
|
+
|
113
|
+
use_stored_proc :list_inactive_clients, :proc_name => 'GET_CLIENTS_INACTIVE_STATUS', :filter => :make_clients
|
114
|
+
|
115
|
+
def self.make_clients(item)
|
116
|
+
Client.new item
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
client_services = ClientServices.new
|
121
|
+
|
122
|
+
# ...
|
123
|
+
|
124
|
+
client_list = client_services.list_inactive_clients(first_of_year)
|
125
|
+
|
126
|
+
# and so on ...
|
127
|
+
```
|
128
|
+
|
129
|
+
### License
|
130
|
+
|
131
|
+
MIT License. Copyright 2013 Leopold O'Donnell
|
132
|
+
|
133
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
begin
|
9
|
+
require 'rdoc/task'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
RDoc::Task = Rake::RDocTask
|
14
|
+
end
|
15
|
+
|
16
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'UsesStoredProcedures'
|
19
|
+
rdoc.options << '--line-numbers'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
Bundler::GemHelper.install_tasks
|
25
|
+
|
26
|
+
desc "Run all specs"
|
27
|
+
RSpec::Core::RakeTask.new(:spec)
|
28
|
+
|
29
|
+
task :default => :spec
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'uses_stored_procedures/hash_with_attributes'
|
2
|
+
|
3
|
+
##
|
4
|
+
# UsesStoredProcedures provides that ability to extend a class with
|
5
|
+
# the ability to call stored_procedures providing an array of hash
|
6
|
+
# entries that can be mapped to an array of other objects.
|
7
|
+
#
|
8
|
+
module UsesStoredProcedures
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
##
|
13
|
+
# Add a stored procedure that can ba called on a class or a
|
14
|
+
# class instance.
|
15
|
+
#
|
16
|
+
# @param [Symbol] name is the symbolic name of the method to be created.
|
17
|
+
# By default, it is assumed that the stored procedure has the same
|
18
|
+
# name as a string.
|
19
|
+
#
|
20
|
+
# @param args is a variable list of optional arguments. Available Options
|
21
|
+
# include:
|
22
|
+
#
|
23
|
+
# * :proc_name - is the name of the stored procedure. Use this if the
|
24
|
+
# name doesn't match the name paramter
|
25
|
+
# * :filter - is a class method used to filter the return results. The
|
26
|
+
# method should take a HashWithAttributes instance and return an object
|
27
|
+
# for the array entry
|
28
|
+
#
|
29
|
+
# @param [Proc] block - is a block that takes a HashWithAttributes instance
|
30
|
+
# and returns an object for the array entry.
|
31
|
+
#
|
32
|
+
# @raises if the current SQL adapter isn't supported.
|
33
|
+
#
|
34
|
+
def uses_stored_proc(name, *args, &block)
|
35
|
+
options = args.extract_options!
|
36
|
+
proc_name = options[:proc_name] || name.to_s
|
37
|
+
|
38
|
+
# Install the row mapper block or method
|
39
|
+
self.install_stored_proc_methods(name, proc_name, call_stored_proc_verb, options[:filter] || block)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Return the SQL verb for calling a stored procedure.
|
44
|
+
def call_stored_proc_verb()
|
45
|
+
case ActiveRecord::Base.connection.adapter_name.to_sym
|
46
|
+
when :Mysql2, :MySQL
|
47
|
+
'call'
|
48
|
+
when :PostgreSQL
|
49
|
+
'select'
|
50
|
+
when :SQLServer
|
51
|
+
'exec'
|
52
|
+
else
|
53
|
+
raise "uses_stored_procedurs does not support your connection adapter"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define the class and instance methods. Note the need to
|
58
|
+
# create a singleton class instance to create the stored
|
59
|
+
# procedure class method.
|
60
|
+
def install_stored_proc_methods(name, proc_name, call_stored_proc_verb, filter) #:nodoc:
|
61
|
+
define_method name do |*args|
|
62
|
+
self.class.exec_stored_proc(proc_name, call_stored_proc_verb, filter, args)
|
63
|
+
end
|
64
|
+
|
65
|
+
singleton_class = class << self; self; end
|
66
|
+
singleton_class.send(:define_method, name) do |*args|
|
67
|
+
exec_stored_proc(proc_name, call_stored_proc_verb, filter, args)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# Run the stored procedure with the arguments provided and send
|
73
|
+
# them through a filter method or block if specified.
|
74
|
+
def exec_stored_proc(name, call_stored_proc_verb, filter, args) #:nodoc:
|
75
|
+
sql = "#{call_stored_proc_verb} #{name} (" + args.map {|a| "'#{a}'"}.join(',') + ")"
|
76
|
+
|
77
|
+
# Call the stored procedure. Note the need to reset the connection
|
78
|
+
# because the mysql connection tends to hangup when stored procedures
|
79
|
+
# are called. Note that testing for ::connected? doesn't work.
|
80
|
+
#
|
81
|
+
# TODO: investigate why stored procedures cause connection hangups.
|
82
|
+
records = ActiveRecord::Base.connection.select_all(sql)
|
83
|
+
ActiveRecord::Base.connection.reconnect!
|
84
|
+
|
85
|
+
filter_stored_proc_results(records, filter)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def filter_stored_proc_results(results, filter)
|
90
|
+
case filter
|
91
|
+
when Symbol
|
92
|
+
# Map with a member function
|
93
|
+
results.map do |r| self.send(filter.to_s, HashWithAttributes.new(r)) end
|
94
|
+
when Proc
|
95
|
+
# Map with a block
|
96
|
+
results.map do |r| filter.call(HashWithAttributes.new(r)) end
|
97
|
+
else
|
98
|
+
# Just map it
|
99
|
+
results.map do |r| HashWithAttributes.new r end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
require 'uses_stored_procedures/railtie'
|
107
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module UsesStoredProcedures
|
2
|
+
##
|
3
|
+
# HashWithAttributes augments Hash with getters and setters on
|
4
|
+
# an as needed basis.
|
5
|
+
#
|
6
|
+
# When ever an instance of HashWithAttributes is sent an accessor
|
7
|
+
# message, or a respond_to? message that matches a hash key for
|
8
|
+
# the first time, a setter and getter is created for that key which
|
9
|
+
# may be either a symbol or a string.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
13
|
+
# h = HashWithAttributes.new
|
14
|
+
# h[:one] = 1
|
15
|
+
# h['two'] = 2
|
16
|
+
# h.one = 3
|
17
|
+
# h.two = 4
|
18
|
+
# puts "One is #{h.one} and Two is #{h.two}"
|
19
|
+
# # > One is 3 and Two is 4
|
20
|
+
#
|
21
|
+
class HashWithAttributes < Hash
|
22
|
+
|
23
|
+
##
|
24
|
+
# Create a new HashWithAttributes.
|
25
|
+
#
|
26
|
+
# @param [Object] hash_or_obj will initialize the hash
|
27
|
+
# from another hash, or work like Hash where the object
|
28
|
+
# is the default for empty entries.
|
29
|
+
#
|
30
|
+
def initialize(hash_or_obj = nil)
|
31
|
+
if hash_or_obj && hash_or_obj.kind_of?(Hash)
|
32
|
+
super()
|
33
|
+
self.merge! hash_or_obj
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(name, *args, &block)
|
40
|
+
if check_and_create_accessors(name)
|
41
|
+
args.length > 0 ? send(name.to_s, args.first) : send(name.to_s)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to?(symbol, include_private = false)
|
48
|
+
super || check_and_create_accessors(symbol)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def check_and_create_accessors(name)
|
53
|
+
item_name = name.to_s.chomp '='
|
54
|
+
if has_key?(item_name) || has_key?(item_name.to_sym)
|
55
|
+
self.class.create_accessors(item_name)
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_accessors(accessor)
|
62
|
+
define_method(accessor) { fetch accessor }
|
63
|
+
define_method("#{accessor}=") { |v| store(accessor, v) }
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uses-store-procedures
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Leo O'Donnell
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.12
|
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: 3.2.12
|
30
|
+
description: ! 'So, you really need to use stored procedures in your Rails application.
|
31
|
+
This gem extends
|
32
|
+
|
33
|
+
ActiveRecord with a class method to add to your models or services.
|
34
|
+
|
35
|
+
'
|
36
|
+
email:
|
37
|
+
- leopold.odonnell@gmail.com
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- lib/tasks/uses-stored-procedures_tasks.rake
|
43
|
+
- lib/uses_stored_procedures/hash_with_attributes.rb
|
44
|
+
- lib/uses_stored_procedures/railtie.rb
|
45
|
+
- lib/uses_stored_procedures/version.rb
|
46
|
+
- lib/uses_stored_procedures.rb
|
47
|
+
- MIT-LICENSE
|
48
|
+
- Rakefile
|
49
|
+
- README.md
|
50
|
+
homepage: https://github.com/leopoldodonnell/uses-stored-procedures
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.24
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Extend ActiveRecord with the ability to use Stored Procedures.
|
74
|
+
test_files: []
|
75
|
+
has_rdoc:
|