spare 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 +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.
|