activerecord-sqlserver-adapter 2.3.10 → 2.3.11
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/CHANGELOG +5 -0
- data/Rakefile +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +88 -15
- data/test/cases/connection_test_sqlserver.rb +64 -62
- data/test/cases/execute_procedure_test_sqlserver.rb +17 -9
- data/test/cases/sqlserver_helper.rb +3 -0
- data/test/connections/native_sqlserver/connection.rb +2 -1
- data/test/connections/native_sqlserver_odbc/connection.rb +2 -1
- metadata +4 -4
data/CHANGELOG
CHANGED
data/Rakefile
CHANGED
@@ -22,7 +22,7 @@ task :test => ['test:odbc']
|
|
22
22
|
|
23
23
|
namespace :test do
|
24
24
|
|
25
|
-
['odbc','adonet'].each do |mode|
|
25
|
+
['dblib','odbc','adonet'].each do |mode|
|
26
26
|
|
27
27
|
Rake::TestTask.new(mode) do |t|
|
28
28
|
t.libs = test_libs(mode)
|
@@ -44,7 +44,7 @@ end
|
|
44
44
|
|
45
45
|
namespace :profile do
|
46
46
|
|
47
|
-
['odbc','adonet'].each do |mode|
|
47
|
+
['dblib','odbc','adonet'].each do |mode|
|
48
48
|
namespace mode.to_sym do
|
49
49
|
|
50
50
|
Dir.glob("test/profile/*_profile_case.rb").sort.each do |test_file|
|
@@ -12,6 +12,9 @@ module ActiveRecord
|
|
12
12
|
config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
|
13
13
|
mode = config[:mode].to_s.downcase.underscore.to_sym
|
14
14
|
case mode
|
15
|
+
when :dblib
|
16
|
+
raise ArgumentError, 'Missing :dataserver configuration.' unless config.has_key?(:dataserver)
|
17
|
+
require_library_or_gem 'tiny_tds'
|
15
18
|
when :odbc
|
16
19
|
require_library_or_gem 'odbc' unless defined?(ODBC)
|
17
20
|
require 'active_record/connection_adapters/sqlserver_adapter/core_ext/odbc'
|
@@ -173,16 +176,18 @@ module ActiveRecord
|
|
173
176
|
class SQLServerAdapter < AbstractAdapter
|
174
177
|
|
175
178
|
ADAPTER_NAME = 'SQLServer'.freeze
|
176
|
-
VERSION = '2.3.
|
179
|
+
VERSION = '2.3.11'.freeze
|
177
180
|
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
|
178
181
|
SUPPORTED_VERSIONS = [2000,2005,2008].freeze
|
179
182
|
LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].to_set.freeze
|
180
183
|
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
181
184
|
LOST_CONNECTION_EXCEPTIONS = {
|
185
|
+
:dblib => ['TinyTds::Error'],
|
182
186
|
:odbc => ['ODBC::Error'],
|
183
187
|
:adonet => ['TypeError','System::Data::SqlClient::SqlException']
|
184
188
|
}
|
185
189
|
LOST_CONNECTION_MESSAGES = {
|
190
|
+
:dblib => [/closed connection/],
|
186
191
|
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
|
187
192
|
:adonet => [/current state is closed/, /network-related/]
|
188
193
|
}
|
@@ -348,6 +353,15 @@ module ActiveRecord
|
|
348
353
|
# CONNECTION MANAGEMENT ====================================#
|
349
354
|
|
350
355
|
def active?
|
356
|
+
connected = case @connection_options[:mode]
|
357
|
+
when :dblib
|
358
|
+
!@connection.closed?
|
359
|
+
when :odbc
|
360
|
+
true
|
361
|
+
else :adonet
|
362
|
+
true
|
363
|
+
end
|
364
|
+
return false if !connected
|
351
365
|
raw_connection_do("SELECT 1")
|
352
366
|
true
|
353
367
|
rescue *lost_connection_exceptions
|
@@ -362,6 +376,8 @@ module ActiveRecord
|
|
362
376
|
|
363
377
|
def disconnect!
|
364
378
|
case @connection_options[:mode]
|
379
|
+
when :dblib
|
380
|
+
@connection.close rescue nil
|
365
381
|
when :odbc
|
366
382
|
@connection.disconnect rescue nil
|
367
383
|
else :adonet
|
@@ -414,8 +430,8 @@ module ActiveRecord
|
|
414
430
|
end
|
415
431
|
|
416
432
|
def execute(sql, name = nil, skip_logging = false)
|
417
|
-
if
|
418
|
-
with_identity_insert_enabled(
|
433
|
+
if id_insert_table_name = query_requires_identity_insert?(sql)
|
434
|
+
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
|
419
435
|
else
|
420
436
|
do_execute(sql,name)
|
421
437
|
end
|
@@ -424,19 +440,27 @@ module ActiveRecord
|
|
424
440
|
def execute_procedure(proc_name, *variables)
|
425
441
|
vars = variables.map{ |v| quote(v) }.join(', ')
|
426
442
|
sql = "EXEC #{proc_name} #{vars}".strip
|
443
|
+
name = 'Execute Procedure'
|
427
444
|
results = []
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
445
|
+
case @connection_options[:mode]
|
446
|
+
when :dblib
|
447
|
+
results << select(sql, name).map { |r| r.with_indifferent_access }
|
448
|
+
when :odbc
|
449
|
+
log(sql, name) do
|
450
|
+
raw_connection_run(sql) do |handle|
|
451
|
+
get_rows = lambda {
|
452
|
+
rows = handle_to_names_and_values handle, :fetch => :all
|
453
|
+
rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
|
454
|
+
results << rows
|
455
|
+
}
|
437
456
|
get_rows.call
|
457
|
+
while handle_more_results?(handle)
|
458
|
+
get_rows.call
|
459
|
+
end
|
438
460
|
end
|
439
461
|
end
|
462
|
+
when :adonet
|
463
|
+
results << select(sql, name).map { |r| r.with_indifferent_access }
|
440
464
|
end
|
441
465
|
results.many? ? results : results.first
|
442
466
|
end
|
@@ -813,6 +837,23 @@ module ActiveRecord
|
|
813
837
|
def connect
|
814
838
|
config = @connection_options
|
815
839
|
@connection = case @connection_options[:mode]
|
840
|
+
when :dblib
|
841
|
+
appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
|
842
|
+
encoding = config[:encoding].present? ? config[:encoding] : nil
|
843
|
+
TinyTds::Client.new({
|
844
|
+
:dataserver => config[:dataserver],
|
845
|
+
:username => config[:username],
|
846
|
+
:password => config[:password],
|
847
|
+
:database => config[:database],
|
848
|
+
:appname => appname,
|
849
|
+
:login_timeout => config[:dblib_login_timeout],
|
850
|
+
:timeout => config[:dblib_timeout],
|
851
|
+
:encoding => encoding
|
852
|
+
}).tap do |client|
|
853
|
+
client.execute("SET ANSI_DEFAULTS ON").do
|
854
|
+
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
855
|
+
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
856
|
+
end
|
816
857
|
when :odbc
|
817
858
|
if config[:dsn].include?(';')
|
818
859
|
driver = ODBC::Driver.new.tap do |d|
|
@@ -889,6 +930,8 @@ module ActiveRecord
|
|
889
930
|
def raw_connection_run(sql)
|
890
931
|
with_auto_reconnect do
|
891
932
|
case @connection_options[:mode]
|
933
|
+
when :dblib
|
934
|
+
@connection.execute(sql)
|
892
935
|
when :odbc
|
893
936
|
block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
|
894
937
|
else :adonet
|
@@ -899,15 +942,21 @@ module ActiveRecord
|
|
899
942
|
|
900
943
|
def raw_connection_do(sql)
|
901
944
|
case @connection_options[:mode]
|
945
|
+
when :dblib
|
946
|
+
@insert_sql ? @connection.execute(sql).insert : @connection.execute(sql).do
|
902
947
|
when :odbc
|
903
948
|
@connection.do(sql)
|
904
949
|
else :adonet
|
905
950
|
@connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
|
906
951
|
end
|
952
|
+
ensure
|
953
|
+
@insert_sql = false
|
954
|
+
@update_sql = false
|
907
955
|
end
|
908
956
|
|
909
957
|
def finish_statement_handle(handle)
|
910
958
|
case @connection_options[:mode]
|
959
|
+
when :dblib
|
911
960
|
when :odbc
|
912
961
|
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
|
913
962
|
when :adonet
|
@@ -924,12 +973,24 @@ module ActiveRecord
|
|
924
973
|
end
|
925
974
|
|
926
975
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
927
|
-
|
976
|
+
@insert_sql = true
|
977
|
+
case @connection_options[:mode]
|
978
|
+
when :dblib
|
979
|
+
execute(sql, name) || id_value
|
980
|
+
else
|
981
|
+
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
982
|
+
end
|
928
983
|
end
|
929
984
|
|
930
985
|
def update_sql(sql, name = nil)
|
931
|
-
|
932
|
-
|
986
|
+
@update_sql = true
|
987
|
+
case @connection_options[:mode]
|
988
|
+
when :dblib
|
989
|
+
execute(sql, name)
|
990
|
+
else
|
991
|
+
execute(sql, name)
|
992
|
+
select_value('SELECT @@ROWCOUNT AS AffectedRows')
|
993
|
+
end
|
933
994
|
end
|
934
995
|
|
935
996
|
def info_schema_query
|
@@ -955,6 +1016,7 @@ module ActiveRecord
|
|
955
1016
|
|
956
1017
|
def handle_more_results?(handle)
|
957
1018
|
case @connection_options[:mode]
|
1019
|
+
when :dblib
|
958
1020
|
when :odbc
|
959
1021
|
handle.more_results
|
960
1022
|
when :adonet
|
@@ -964,12 +1026,23 @@ module ActiveRecord
|
|
964
1026
|
|
965
1027
|
def handle_to_names_and_values(handle, options={})
|
966
1028
|
case @connection_options[:mode]
|
1029
|
+
when :dblib
|
1030
|
+
handle_to_names_and_values_dblib(handle, options)
|
967
1031
|
when :odbc
|
968
1032
|
handle_to_names_and_values_odbc(handle, options)
|
969
1033
|
when :adonet
|
970
1034
|
handle_to_names_and_values_adonet(handle, options)
|
971
1035
|
end
|
972
1036
|
end
|
1037
|
+
|
1038
|
+
def handle_to_names_and_values_dblib(handle, options={})
|
1039
|
+
query_options = {}.tap do |qo|
|
1040
|
+
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
|
1041
|
+
qo[:first] = true if options[:fetch] == :one
|
1042
|
+
qo[:as] = options[:fetch] == :rows ? :array : :hash
|
1043
|
+
end
|
1044
|
+
handle.each(query_options)
|
1045
|
+
end
|
973
1046
|
|
974
1047
|
def handle_to_names_and_values_odbc(handle, options={})
|
975
1048
|
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
|
@@ -12,24 +12,6 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
|
|
12
12
|
end
|
13
13
|
|
14
14
|
|
15
|
-
should 'return finished ODBC statment handle from #execute without block' do
|
16
|
-
assert_all_statements_used_are_closed do
|
17
|
-
@connection.execute('SELECT * FROM [topics]')
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
should 'finish ODBC statment handle from #execute with block' do
|
22
|
-
assert_all_statements_used_are_closed do
|
23
|
-
@connection.execute('SELECT * FROM [topics]') { }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
should 'finish connection from #raw_select' do
|
28
|
-
assert_all_statements_used_are_closed do
|
29
|
-
@connection.send(:raw_select,'SELECT * FROM [topics]')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
15
|
should 'affect rows' do
|
34
16
|
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
|
35
17
|
updated = Topic.update(topic_data.keys, topic_data.values)
|
@@ -39,38 +21,6 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
|
|
39
21
|
assert_equal 2, Topic.delete([1, 2])
|
40
22
|
end
|
41
23
|
|
42
|
-
should 'execute without block closes statement' do
|
43
|
-
assert_all_statements_used_are_closed do
|
44
|
-
@connection.execute("SELECT 1")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
should 'execute with block closes statement' do
|
49
|
-
assert_all_statements_used_are_closed do
|
50
|
-
@connection.execute("SELECT 1") do |sth|
|
51
|
-
assert !sth.finished?, "Statement should still be alive within block"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
should 'insert with identity closes statement' do
|
57
|
-
assert_all_statements_used_are_closed do
|
58
|
-
@connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
should 'insert without identity closes statement' do
|
63
|
-
assert_all_statements_used_are_closed do
|
64
|
-
@connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
should 'active closes statement' do
|
69
|
-
assert_all_statements_used_are_closed do
|
70
|
-
@connection.active?
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
24
|
should 'allow usage of :database connection option to remove setting from dsn' do
|
75
25
|
assert_equal 'activerecord_unittest', @connection.current_database
|
76
26
|
begin
|
@@ -82,6 +32,61 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
|
|
82
32
|
end
|
83
33
|
end
|
84
34
|
|
35
|
+
context 'ODBC connection management' do
|
36
|
+
|
37
|
+
should 'return finished ODBC statement handle from #execute without block' do
|
38
|
+
assert_all_odbc_statements_used_are_closed do
|
39
|
+
@connection.execute('SELECT * FROM [topics]')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'finish ODBC statement handle from #execute with block' do
|
44
|
+
assert_all_odbc_statements_used_are_closed do
|
45
|
+
@connection.execute('SELECT * FROM [topics]') { }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
should 'finish connection from #raw_select' do
|
50
|
+
assert_all_odbc_statements_used_are_closed do
|
51
|
+
@connection.send(:raw_select,'SELECT * FROM [topics]')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'execute without block closes statement' do
|
56
|
+
assert_all_odbc_statements_used_are_closed do
|
57
|
+
@connection.execute("SELECT 1")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
should 'execute with block closes statement' do
|
62
|
+
assert_all_odbc_statements_used_are_closed do
|
63
|
+
@connection.execute("SELECT 1") do |sth|
|
64
|
+
assert !sth.finished?, "Statement should still be alive within block"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
should 'insert with identity closes statement' do
|
70
|
+
assert_all_odbc_statements_used_are_closed do
|
71
|
+
@connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'insert without identity closes statement' do
|
76
|
+
assert_all_odbc_statements_used_are_closed do
|
77
|
+
@connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
should 'active closes statement' do
|
82
|
+
assert_all_odbc_statements_used_are_closed do
|
83
|
+
@connection.active?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end if connection_mode_odbc?
|
88
|
+
|
89
|
+
|
85
90
|
context 'Connection management' do
|
86
91
|
|
87
92
|
setup do
|
@@ -116,21 +121,18 @@ class ConnectionTestSqlserver < ActiveRecord::TestCase
|
|
116
121
|
|
117
122
|
private
|
118
123
|
|
119
|
-
def
|
124
|
+
def assert_all_odbc_statements_used_are_closed(&block)
|
125
|
+
odbc = @connection.raw_connection.class.parent
|
120
126
|
existing_handles = []
|
121
|
-
ObjectSpace.each_object(
|
122
|
-
|
127
|
+
ObjectSpace.each_object(odbc::Statement) { |h| existing_handles << h }
|
128
|
+
existing_handle_ids = existing_handles.map(&:object_id)
|
129
|
+
assert existing_handles.all?(&:finished?), "Somewhere before the block some statements were not closed"
|
130
|
+
GC.disable
|
123
131
|
yield
|
124
132
|
used_handles = []
|
125
|
-
ObjectSpace.each_object(
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
ObjectSpace.each_object(ODBC::Statement) do |handle|
|
130
|
-
assert_block "Statement should have been closed within given block" do
|
131
|
-
handle.finished?
|
132
|
-
end
|
133
|
-
end
|
133
|
+
ObjectSpace.each_object(odbc::Statement) { |h| used_handles << h unless existing_handle_ids.include?(h.object_id) }
|
134
|
+
assert used_handles.size > 0, "No statements were used within given block"
|
135
|
+
assert used_handles.all?(&:finished?), "Statement should have been closed within given block"
|
134
136
|
ensure
|
135
137
|
GC.enable
|
136
138
|
end
|
@@ -20,15 +20,23 @@ class ExecuteProcedureTestSqlserver < ActiveRecord::TestCase
|
|
20
20
|
assert_equal 'TABLE', table_info[:TABLE_TYPE], "Table Info: #{table_info.inspect}"
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
if connection_mode_odbc?
|
24
|
+
|
25
|
+
should 'allow multiple result sets to be returned' do
|
26
|
+
results1, results2 = @klass.execute_procedure('sp_helpconstraint','accounts')
|
27
|
+
assert_instance_of Array, results1
|
28
|
+
assert_instance_of HashWithIndifferentAccess, results1.first
|
29
|
+
assert results1.first['Object Name']
|
30
|
+
assert_instance_of Array, results2
|
31
|
+
assert_instance_of HashWithIndifferentAccess, results2.first
|
32
|
+
assert results2.first['constraint_name']
|
33
|
+
assert results2.first['constraint_type']
|
34
|
+
end
|
35
|
+
|
36
|
+
else
|
37
|
+
|
38
|
+
should 'allow multiple result sets to be returned'
|
39
|
+
|
32
40
|
end
|
33
41
|
|
34
42
|
|
@@ -104,6 +104,7 @@ end
|
|
104
104
|
module ActiveRecord
|
105
105
|
class TestCase < ActiveSupport::TestCase
|
106
106
|
class << self
|
107
|
+
def connection_mode_dblib? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :dblib ; end
|
107
108
|
def connection_mode_odbc? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :odbc ; end
|
108
109
|
def sqlserver_2000? ; ActiveRecord::Base.connection.sqlserver_2000? ; end
|
109
110
|
def sqlserver_2005? ; ActiveRecord::Base.connection.sqlserver_2005? ; end
|
@@ -122,6 +123,8 @@ module ActiveRecord
|
|
122
123
|
end
|
123
124
|
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
|
124
125
|
end
|
126
|
+
def connection_mode_dblib? ; self.class.connection_mode_dblib? ; end
|
127
|
+
def connection_mode_odbc? ; self.class.connection_mode_odbc? ; end
|
125
128
|
def sqlserver_2000? ; self.class.sqlserver_2000? ; end
|
126
129
|
def sqlserver_2005? ; self.class.sqlserver_2005? ; end
|
127
130
|
def sqlserver_2008? ; self.class.sqlserver_2008? ; end
|
@@ -2,7 +2,8 @@ print "Using SQLServer via ADONET\n"
|
|
2
2
|
require_dependency 'models/course'
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
-
ActiveRecord::Base.logger = Logger.new(
|
5
|
+
ActiveRecord::Base.logger = Logger.new(File.expand_path(File.join(SQLSERVER_TEST_ROOT,'debug.log')))
|
6
|
+
ActiveRecord::Base.logger.level = 0
|
6
7
|
|
7
8
|
ActiveRecord::Base.configurations = {
|
8
9
|
'arunit' => {
|
@@ -2,7 +2,8 @@ print "Using SQLServer via ODBC\n"
|
|
2
2
|
require_dependency 'models/course'
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
-
ActiveRecord::Base.logger = Logger.new(
|
5
|
+
ActiveRecord::Base.logger = Logger.new(File.expand_path(File.join(SQLSERVER_TEST_ROOT,'debug.log')))
|
6
|
+
ActiveRecord::Base.logger.level = 0
|
6
7
|
|
7
8
|
ActiveRecord::Base.configurations = {
|
8
9
|
'arunit' => {
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-sqlserver-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 2.3.
|
9
|
+
- 11
|
10
|
+
version: 2.3.11
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ken Collins
|
@@ -19,7 +19,7 @@ autorequire:
|
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
21
|
|
22
|
-
date: 2010-
|
22
|
+
date: 2010-10-17 00:00:00 -04:00
|
23
23
|
default_executable:
|
24
24
|
dependencies:
|
25
25
|
- !ruby/object:Gem::Dependency
|