spare 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 +4 -4
- data/lib/spare/exceptions.rb +1 -0
- data/lib/spare/execution.rb +62 -0
- data/lib/spare/mysql_abstract_adapter.rb +68 -1
- data/lib/spare/stored_procedure.rb +3 -68
- data/lib/spare/version.rb +1 -1
- data/lib/spare.rb +1 -0
- data/spec/lib/spare/mysql_abstract_adapter_spec.rb +9 -8
- data/spec/lib/spare/stored_procedure_spec.rb +41 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eebcabc4cfa5cc1016e4792fcb33d1d3ad3f8ecc
|
4
|
+
data.tar.gz: dd194a9dcb89d2b274f3bbe511d0545ff25320c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01199811e2ceb6df16f47ee095bc9786a7d02a65172ff4f68fbbb3ed299467f1050a2a6a9976e3a153112b9de11fd2208d32a46dfa01d117995ed0f7a27643b5
|
7
|
+
data.tar.gz: 33888abe01749bd5b32c951f8e66eb7b1d5b63477417509ae41eedc0d7e643eb6bceedb1979caf4457344e57868d8d867de74c99f32c464330fc678eeed61275
|
data/lib/spare/exceptions.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Spare
|
2
|
+
module Execution
|
3
|
+
module ClassMethods
|
4
|
+
# Build an object (or multiple objects) and executes, if validations pass.
|
5
|
+
# The resulting object is returned whether the object was executed successfully to the database or not.
|
6
|
+
#
|
7
|
+
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
|
8
|
+
# attributes on the objects that are to be created.
|
9
|
+
def execute(attributes = nil)
|
10
|
+
if attributes.is_a?(Array)
|
11
|
+
attributes.map { |attr| excute(attr) }
|
12
|
+
else
|
13
|
+
object = new(attributes)
|
14
|
+
object.execute
|
15
|
+
object
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias :call :execute
|
19
|
+
|
20
|
+
# Build an object (or multiple objects) and executes,
|
21
|
+
# if validations pass. Raises a RecordInvalid error if validations fail,
|
22
|
+
# unlike Base#create.
|
23
|
+
#
|
24
|
+
# The +attributes+ parameter can be either a Hash or an Array of Hashes.
|
25
|
+
# These describe which attributes to be created on the object, or
|
26
|
+
# multiple objects when given an Array of Hashes.
|
27
|
+
def execute!(attributes = nil)
|
28
|
+
if attributes.is_a?(Array)
|
29
|
+
attributes.collect { |attr| create!(attr) }
|
30
|
+
else
|
31
|
+
object = new(attributes)
|
32
|
+
object.execute!
|
33
|
+
object
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias :call! :execute!
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor :call_results
|
40
|
+
|
41
|
+
def execute
|
42
|
+
if valid?
|
43
|
+
self.class.connection_pool.with_connection do |conn|
|
44
|
+
call_results = conn.execute_stored_procedure(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
valid?
|
48
|
+
end
|
49
|
+
alias :call :execute
|
50
|
+
|
51
|
+
def execute!
|
52
|
+
unless valid?
|
53
|
+
raise(ActiveRecord::StoredProcedureNotExecuted.new("Failed to execute the stored procedure", self))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias :call! :execute!
|
57
|
+
|
58
|
+
def self.included(base)
|
59
|
+
base.extend(Spare::Execution::ClassMethods)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -44,12 +44,79 @@ module ActiveRecord
|
|
44
44
|
else
|
45
45
|
column = new_column(field_name, nil, sql_type, false, collation)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
column.param_type = param_type
|
49
49
|
params << column
|
50
50
|
end
|
51
51
|
params
|
52
52
|
end
|
53
|
+
|
54
|
+
def stored_procedure_to_sql(sp)
|
55
|
+
sql = []
|
56
|
+
sql << sp_inout_sql(sp)
|
57
|
+
sql << sp_call_sql(sp)
|
58
|
+
sql << sp_out_sql(sp)
|
59
|
+
sql.compact!
|
60
|
+
sql.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute_stored_procedure(sp)
|
64
|
+
|
65
|
+
call_results = execute(stored_procedure_to_sql(sp))
|
66
|
+
|
67
|
+
if sp_out_params(sp).length != 0
|
68
|
+
clnt = instance_variable_get(:@connection)
|
69
|
+
while clnt.next_result
|
70
|
+
if result_array = clnt.store_result.to_a[0]
|
71
|
+
sp_out_params(sp).each_with_index do |param,i|
|
72
|
+
sp.__send__ "#{param.name}=", result_array[i]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
else
|
78
|
+
call_results
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def sp_in_params(sp)
|
85
|
+
prms = []
|
86
|
+
sp.class.stored_procedure[:param_list].each do |param|
|
87
|
+
if param.param_type == "IN"
|
88
|
+
prms << quote(sp.read_attribute(param.name))
|
89
|
+
else # OUT
|
90
|
+
prms << "@#{param.name}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
prms
|
94
|
+
end
|
95
|
+
|
96
|
+
def sp_out_params(sp)
|
97
|
+
sp.class.stored_procedure[:param_list].select { |param| param.param_type.to_s =~ /out/i }
|
98
|
+
end
|
99
|
+
|
100
|
+
def sp_inout_params(sp)
|
101
|
+
sp.class.stored_procedure[:param_list].select { |param| param.param_type.to_s =~ /inout/i }
|
102
|
+
end
|
103
|
+
|
104
|
+
def sp_out_sql(sp)
|
105
|
+
"SELECT #{sp_out_params(sp).collect{|param| "@#{param.name}"}.join(',')};"
|
106
|
+
end
|
107
|
+
|
108
|
+
# In MySQL even with multi-statements flag set, variables must be set 1 at a time, so return an array
|
109
|
+
def sp_inout_sql(sp)
|
110
|
+
sql = []
|
111
|
+
sp_inout_params(sp).each do |param|
|
112
|
+
sql << "SET @#{param.name} = #{quote(sp.send(param.name))};"
|
113
|
+
end
|
114
|
+
sql
|
115
|
+
end
|
116
|
+
|
117
|
+
def sp_call_sql(sp)
|
118
|
+
"CALL #{sp.class.stored_procedure[:db]}.#{sp.class.stored_procedure[:specific_name]}(#{sp_in_params(sp).join(',')});"
|
119
|
+
end
|
53
120
|
end
|
54
121
|
end
|
55
122
|
end
|
@@ -2,89 +2,24 @@ require "active_record"
|
|
2
2
|
module ActiveRecord
|
3
3
|
|
4
4
|
# TODO - Refactor using only those modules necessary for things to work, should
|
5
|
-
# be a lot easier when updated to support only Rails 4. Also, move any methods here
|
5
|
+
# be a lot easier when updated to support only Rails 4. Also, move any methods here
|
6
6
|
# to their own module
|
7
7
|
class StoredProcedure < Base
|
8
8
|
|
9
9
|
extend Spare::Core
|
10
10
|
extend Spare::ModelSchema
|
11
11
|
extend Spare::Attributes
|
12
|
+
include Spare::Execution
|
12
13
|
|
13
14
|
self.pluralize_table_names = false # Stored procedure names are what they are.
|
14
15
|
self.abstract_class = true
|
15
16
|
|
16
17
|
attr_accessor :call_results
|
17
18
|
|
18
|
-
def in_params
|
19
|
-
@in_params ||= in_fetch_params
|
20
|
-
end
|
21
|
-
|
22
|
-
def in_fetch_params
|
23
|
-
prms = []
|
24
|
-
self.class.stored_procedure[:param_list].each do |param|
|
25
|
-
if param.param_type == "IN"
|
26
|
-
prms << self.class.connection.quote(self.send(param.name.to_sym))
|
27
|
-
else # OUT
|
28
|
-
prms << "@#{param.name}"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
prms
|
32
|
-
end
|
33
|
-
|
34
|
-
def out_params
|
35
|
-
@out_params ||= self.class.stored_procedure[:param_list].select{|param| param.param_type.to_s =~ /out/i}
|
36
|
-
end
|
37
|
-
|
38
|
-
def inout_params
|
39
|
-
@inout_params ||= self.class.stored_procedure[:param_list].select{|param| param.param_type.to_s =~ /inout/i}
|
40
|
-
end
|
41
|
-
|
42
|
-
def out_sql
|
43
|
-
"SELECT #{out_params.collect{|param| "@#{param.name}"}.join(',')};"
|
44
|
-
end
|
45
|
-
|
46
|
-
# In MySQL even with multi-statements flag set variables must be set 1 at a time, so return an array
|
47
|
-
def inout_sql
|
48
|
-
sql = []
|
49
|
-
inout_params.each do |param|
|
50
|
-
sql << "SET @#{param.name} = #{self.class.connection.quote(send(param.name))}"
|
51
|
-
end
|
52
|
-
sql
|
53
|
-
end
|
54
|
-
|
55
|
-
def call_sql
|
56
|
-
"CALL #{self.class.stored_procedure[:db]}.#{self.class.stored_procedure[:specific_name]}(#{in_params.join(',')});"
|
57
|
-
end
|
58
|
-
|
59
19
|
def to_sql(skip_valid=false)
|
60
20
|
if skip_valid || valid?
|
61
|
-
|
62
|
-
sql = call_sql
|
63
|
-
sql << out_sql unless out_params.blank?
|
64
|
-
sql
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def execute
|
69
|
-
if valid?
|
70
|
-
conn = self.class.connection
|
71
|
-
unless inout_params.blank?
|
72
|
-
self.inout_sql.each do |inout_to_set|
|
73
|
-
conn.execute(inout_to_set)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
self.call_results = conn.execute(self.to_sql(true))
|
77
|
-
if out_params.length != 0
|
78
|
-
clnt = conn.instance_variable_get(:@connection)
|
79
|
-
while clnt.next_result
|
80
|
-
result_array = clnt.store_result.to_a[0]
|
81
|
-
out_params.each_with_index do |param,i|
|
82
|
-
send "#{param.name}=", result_array[i]
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
21
|
+
self.class.connection.stored_procedure_to_sql(self)
|
86
22
|
end
|
87
|
-
valid?
|
88
23
|
end
|
89
24
|
end
|
90
25
|
end
|
data/lib/spare/version.rb
CHANGED
data/lib/spare.rb
CHANGED
@@ -21,13 +21,13 @@ describe ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter do
|
|
21
21
|
END})
|
22
22
|
end
|
23
23
|
context "called with the stored procedure's name" do
|
24
|
-
let (:sp) {ActiveRecord::Base.connection.stored_procedure("sp_test_adapter")}
|
24
|
+
let (:sp) { ActiveRecord::Base.connection.stored_procedure("sp_test_adapter") }
|
25
25
|
it {expect(sp).to be_a(Hash)}
|
26
26
|
it {expect(sp[:param_list]).to be_a(Array)}
|
27
27
|
end
|
28
28
|
|
29
29
|
context "called the stored procedure and database name" do
|
30
|
-
let (:sp) {ActiveRecord::Base.connection.stored_procedure("sp_test.sp_test_adapter")}
|
30
|
+
let (:sp) { ActiveRecord::Base.connection.stored_procedure("sp_test.sp_test_adapter") }
|
31
31
|
it {expect(sp).to be_a(Hash)}
|
32
32
|
it {expect(sp[:param_list]).to be_a(Array)}
|
33
33
|
end
|
@@ -35,12 +35,13 @@ describe ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
describe '#stored_procedure_params' do
|
38
|
-
let (:sp_params)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
let (:sp_params) do
|
39
|
+
%q{IN p_name VARCHAR(255) ,
|
40
|
+
IN p_bar DECIMAL(10,2) ,
|
41
|
+
IN p_other DATE ,
|
42
|
+
OUT results INT(11)}
|
43
|
+
end
|
44
|
+
let (:parsed_params) { ActiveRecord::Base.connection.stored_procedure_params(sp_params, 'utf8_general_ci') }
|
44
45
|
|
45
46
|
it {expect(parsed_params).to be_a(Array)}
|
46
47
|
it {expect(parsed_params.length).to eql(4)}
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'byebug'
|
3
|
-
class SpInsert < ActiveRecord::StoredProcedure
|
3
|
+
class SpInsert < ActiveRecord::StoredProcedure
|
4
|
+
validates :p_name, presence: true
|
5
|
+
end
|
4
6
|
|
5
7
|
class Foo < ActiveRecord::Base;end
|
6
8
|
|
@@ -14,20 +16,25 @@ describe ActiveRecord::StoredProcedure do
|
|
14
16
|
end
|
15
17
|
|
16
18
|
describe '#execute' do
|
17
|
-
let (:sp_insert) {SpInsert.new(:p_name => "foo",:p_deci => 2.0, :p_date => Date.today, :in_out_add => 4)}
|
19
|
+
let (:sp_insert) { SpInsert.new(:p_name => "foo",:p_deci => 2.0, :p_date => Date.today, :in_out_add => 4) }
|
20
|
+
|
18
21
|
it { expect(sp_insert).to be_valid}
|
22
|
+
|
19
23
|
it { expect(sp_insert.execute).to eql(true) }
|
24
|
+
|
20
25
|
it "should work" do
|
21
26
|
expect {
|
22
27
|
sp_insert.execute
|
23
28
|
}.to change(Foo, :count).by(1)
|
24
29
|
end
|
30
|
+
|
25
31
|
context "out parameters" do
|
26
32
|
it "should work" do
|
27
33
|
sp_insert.execute
|
28
34
|
expect(sp_insert.o_id).to be_a(Fixnum)
|
29
35
|
end
|
30
36
|
end
|
37
|
+
|
31
38
|
context "inout parameters" do
|
32
39
|
it "should work" do
|
33
40
|
sp_insert.execute
|
@@ -35,4 +42,36 @@ describe ActiveRecord::StoredProcedure do
|
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
45
|
+
|
46
|
+
context "class methods" do
|
47
|
+
context "#execute" do
|
48
|
+
it "should work" do
|
49
|
+
expect {
|
50
|
+
SpInsert.execute(:p_name => "foo",:p_deci => 2.0, :p_date => Date.today, :in_out_add => 4)
|
51
|
+
}.to change(Foo, :count).by(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
context "out parameters" do
|
55
|
+
it "should work" do
|
56
|
+
sp = SpInsert.execute(:p_name => "foo",:p_deci => 2.0, :p_date => Date.today, :in_out_add => 4)
|
57
|
+
expect(sp.o_id).to be_a(Fixnum)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "inout parameters" do
|
62
|
+
it "should work" do
|
63
|
+
sp = SpInsert.execute(:p_name => "foo",:p_deci => 2.0, :p_date => Date.today, :in_out_add => 4)
|
64
|
+
expect(sp.in_out_add).to eql(5)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "#execute!" do
|
70
|
+
context "not valid" do
|
71
|
+
it "should work" do
|
72
|
+
expect { SpInsert.execute! }.to raise_error(ActiveRecord::StoredProcedureNotExecuted)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
38
77
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spare
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Mckinney
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/spare/attributes.rb
|
118
118
|
- lib/spare/core.rb
|
119
119
|
- lib/spare/exceptions.rb
|
120
|
+
- lib/spare/execution.rb
|
120
121
|
- lib/spare/model_schema.rb
|
121
122
|
- lib/spare/mysql_abstract_adapter.rb
|
122
123
|
- lib/spare/schema_cache.rb
|
@@ -149,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
150
|
version: '0'
|
150
151
|
requirements: []
|
151
152
|
rubyforge_project:
|
152
|
-
rubygems_version: 2.
|
153
|
+
rubygems_version: 2.6.4
|
153
154
|
signing_key:
|
154
155
|
specification_version: 4
|
155
156
|
summary: StoredProcedure models for ActiveRecord.
|