activerecord-jdbc-adapter 1.2.1 → 1.2.2

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.
Files changed (53) hide show
  1. data/.travis.yml +3 -0
  2. data/Gemfile.lock +13 -15
  3. data/History.txt +19 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +2 -1
  6. data/lib/arel/visitors/derby.rb +9 -2
  7. data/lib/arel/visitors/sql_server.rb +2 -0
  8. data/lib/arjdbc/db2/adapter.rb +3 -1
  9. data/lib/arjdbc/derby/adapter.rb +10 -3
  10. data/lib/arjdbc/jdbc/adapter.rb +5 -2
  11. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  12. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  13. data/lib/arjdbc/jdbc/connection.rb +5 -1
  14. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +1 -0
  15. data/lib/arjdbc/mssql/adapter.rb +31 -28
  16. data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
  17. data/lib/arjdbc/mysql/adapter.rb +110 -45
  18. data/lib/arjdbc/oracle/adapter.rb +7 -0
  19. data/lib/arjdbc/postgresql/adapter.rb +327 -153
  20. data/lib/arjdbc/sqlite3/adapter.rb +9 -4
  21. data/lib/arjdbc/version.rb +1 -1
  22. data/rakelib/db.rake +17 -5
  23. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +14 -4
  24. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -0
  25. data/test/db/jdbc.rb +4 -3
  26. data/test/db2_reset_column_information_test.rb +8 -0
  27. data/test/derby_reset_column_information_test.rb +8 -0
  28. data/test/derby_row_locking_test.rb +9 -0
  29. data/test/derby_simple_test.rb +40 -0
  30. data/test/h2_simple_test.rb +2 -2
  31. data/test/helper.rb +15 -2
  32. data/test/jdbc_common.rb +1 -0
  33. data/test/jndi_callbacks_test.rb +5 -9
  34. data/test/manualTestDatabase.rb +31 -31
  35. data/test/models/validates_uniqueness_of_string.rb +1 -1
  36. data/test/mssql_ignore_system_views_test.rb +27 -0
  37. data/test/mssql_null_test.rb +14 -0
  38. data/test/mssql_reset_column_information_test.rb +8 -0
  39. data/test/mssql_row_locking_sql_test.rb +159 -0
  40. data/test/mssql_row_locking_test.rb +9 -0
  41. data/test/mysql_reset_column_information_test.rb +8 -0
  42. data/test/mysql_simple_test.rb +69 -5
  43. data/test/oracle_reset_column_information_test.rb +8 -0
  44. data/test/oracle_specific_test.rb +1 -1
  45. data/test/postgres_nonseq_pkey_test.rb +1 -1
  46. data/test/postgres_reset_column_information_test.rb +8 -0
  47. data/test/postgres_simple_test.rb +72 -1
  48. data/test/row_locking.rb +90 -0
  49. data/test/simple.rb +82 -2
  50. data/test/sqlite3_reset_column_information_test.rb +8 -0
  51. data/test/sqlite3_simple_test.rb +47 -0
  52. data/test/sybase_reset_column_information_test.rb +8 -0
  53. metadata +33 -3
@@ -147,7 +147,7 @@ module ::ArJdbc
147
147
 
148
148
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) #:nodoc:
149
149
  sql = substitute_binds(sql, binds)
150
- @connection.execute_update(sql)
150
+ log(sql, name) { @connection.execute_update(sql) }
151
151
  id_value || last_insert_id
152
152
  end
153
153
 
@@ -208,9 +208,12 @@ module ::ArJdbc
208
208
  end
209
209
 
210
210
  def table_structure(table_name)
211
- structure = @connection.execute_query("PRAGMA table_info(#{quote_table_name(table_name)})")
212
- raise ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'" if structure.empty?
213
- structure
211
+ sql = "PRAGMA table_info(#{quote_table_name(table_name)})"
212
+ log(sql, 'SCHEMA') { @connection.execute_query(sql) }
213
+ rescue ActiveRecord::JDBCError => error
214
+ e = ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'")
215
+ e.set_backtrace error.backtrace
216
+ raise e
214
217
  end
215
218
 
216
219
  def jdbc_columns(table_name, name = nil) #:nodoc:
@@ -283,6 +286,8 @@ module ::ArJdbc
283
286
  self.limit = options[:limit] if options.include?(:limit)
284
287
  self.default = options[:default] if include_default
285
288
  self.null = options[:null] if options.include?(:null)
289
+ self.precision = options[:precision] if options.include?(:precision)
290
+ self.scale = options[:scale] if options.include?(:scale)
286
291
  end
287
292
  end
288
293
  end
@@ -1,6 +1,6 @@
1
1
  module ArJdbc
2
2
  module Version
3
- VERSION = "1.2.1"
3
+ VERSION = "1.2.2"
4
4
  end
5
5
  end
6
6
  # Compatibility with older versions of ar-jdbc for other extensions out there
@@ -2,26 +2,38 @@ namespace :db do
2
2
  desc "Creates the test database for MySQL."
3
3
  task :mysql do
4
4
  load 'test/db/mysql.rb' rescue nil
5
- IO.popen("mysql -u root", "w") do |io|
6
- io.puts <<-SQL
5
+ t = Tempfile.new("mysql")
6
+ t.puts <<-SQL
7
7
  DROP DATABASE IF EXISTS `#{MYSQL_CONFIG[:database]}`;
8
8
  CREATE DATABASE `#{MYSQL_CONFIG[:database]}` DEFAULT CHARACTER SET `utf8`;
9
9
  GRANT ALL PRIVILEGES ON `#{MYSQL_CONFIG[:database]}`.* TO #{MYSQL_CONFIG[:username]}@localhost;
10
+ GRANT ALL PRIVILEGES ON `test\_%`.* TO #{MYSQL_CONFIG[:username]}@localhost;
10
11
  SET PASSWORD FOR #{MYSQL_CONFIG[:username]}@localhost = PASSWORD('#{MYSQL_CONFIG[:password]}');
11
12
  SQL
13
+ t.close
14
+ at_exit { t.unlink }
15
+ password = ""
16
+ if ENV['DATABASE_YML']
17
+ require 'yaml'
18
+ password = YAML.load(File.new(ENV['DATABASE_YML']))["production"]["password"]
19
+ password_arg = " --password=#{password}"
12
20
  end
21
+ sh "cat #{t.path} | mysql -u root#{password_arg}", :verbose => false # so password is not echoed
13
22
  end
14
23
 
15
24
  desc "Creates the test database for PostgreSQL."
16
25
  task :postgres do
26
+ fail unless have_postgres?
17
27
  load 'test/db/postgres.rb' rescue nil
18
- IO.popen("psql", "w") do |io|
19
- io.puts <<-SQL
28
+ t = Tempfile.new("psql")
29
+ t.puts <<-SQL
20
30
  DROP DATABASE IF EXISTS #{POSTGRES_CONFIG[:database]};
21
31
  DROP USER IF EXISTS #{POSTGRES_CONFIG[:username]};
22
32
  CREATE USER #{POSTGRES_CONFIG[:username]} CREATEDB SUPERUSER LOGIN PASSWORD '#{POSTGRES_CONFIG[:password]}';
23
33
  CREATE DATABASE #{POSTGRES_CONFIG[:database]} OWNER #{POSTGRES_CONFIG[:username]};
24
34
  SQL
25
- end
35
+ t.close
36
+ at_exit { t.unlink }
37
+ sh "cat #{t.path} | psql -U postgres"
26
38
  end
27
39
  end
@@ -970,11 +970,13 @@ public class RubyJdbcConnection extends RubyObject {
970
970
  return RubyString.newUnicodeString(runtime, string);
971
971
  }
972
972
 
973
- private static final int TABLE_NAME = 3;
974
973
 
975
974
  protected SQLBlock tableLookupBlock(final Ruby runtime,
976
975
  final String catalog, final String schemapat,
977
976
  final String tablepat, final String[] types, final boolean downCase) {
977
+ final int TABLE_SCHEM = 2;
978
+ final int TABLE_NAME = 3;
979
+ final int TABLE_TYPE = 4;
978
980
  return new SQLBlock() {
979
981
  public Object call(Connection c) throws SQLException {
980
982
  ResultSet rs = null;
@@ -982,7 +984,8 @@ public class RubyJdbcConnection extends RubyObject {
982
984
  DatabaseMetaData metadata = c.getMetaData();
983
985
  String clzName = metadata.getClass().getName().toLowerCase();
984
986
  boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
985
- boolean isDerby = clzName.indexOf("derby") != 1;
987
+ boolean isDerby = clzName.indexOf("derby") != -1;
988
+ boolean isMssql = clzName.indexOf("sqlserver") != -1 || clzName.indexOf("tds") != -1;
986
989
 
987
990
  String realschema = schemapat;
988
991
  String realtablepat = tablepat;
@@ -995,6 +998,7 @@ public class RubyJdbcConnection extends RubyObject {
995
998
  List arr = new ArrayList();
996
999
  while (rs.next()) {
997
1000
  String name;
1001
+ String schema = rs.getString(TABLE_SCHEM) != null ? rs.getString(TABLE_SCHEM).toLowerCase() : null;
998
1002
 
999
1003
  if (downCase) {
1000
1004
  name = rs.getString(TABLE_NAME).toLowerCase();
@@ -1002,9 +1006,15 @@ public class RubyJdbcConnection extends RubyObject {
1002
1006
  name = caseConvertIdentifierForRails(metadata, rs.getString(TABLE_NAME));
1003
1007
  }
1004
1008
  // Handle stupid Oracle 10g RecycleBin feature
1005
- if (!isOracle || !name.startsWith("bin$")) {
1006
- arr.add(RubyString.newUnicodeString(runtime, name));
1009
+ if (isOracle && name.startsWith("bin$")) {
1010
+ continue;
1007
1011
  }
1012
+ // Under mssql, don't return system tables/views unless they're explicitly asked for.
1013
+ if (isMssql && realschema==null &&
1014
+ ("sys".equals(schema) || "information_schema".equals(schema))) {
1015
+ continue;
1016
+ }
1017
+ arr.add(RubyString.newUnicodeString(runtime, name));
1008
1018
  }
1009
1019
  return runtime.newArray(arr);
1010
1020
  } finally {
@@ -27,6 +27,10 @@ package arjdbc.postgresql;
27
27
 
28
28
  import arjdbc.jdbc.RubyJdbcConnection;
29
29
 
30
+ import java.sql.ResultSet;
31
+ import java.sql.SQLException;
32
+ import java.sql.Types;
33
+
30
34
  import org.jruby.Ruby;
31
35
  import org.jruby.RubyClass;
32
36
  import org.jruby.runtime.ObjectAllocator;
@@ -49,6 +53,27 @@ public class PostgresqlRubyJdbcConnection extends RubyJdbcConnection {
49
53
  return clazz;
50
54
  }
51
55
 
56
+ /**
57
+ * Override jdbcToRuby type conversions to handle infinite timestamps.
58
+ * Handing timestamp off to ruby as string so adapter can perform type
59
+ * conversion to timestamp
60
+ */
61
+ @Override
62
+ protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type,
63
+ ResultSet resultSet)
64
+ throws SQLException {
65
+ if(type == Types.TIMESTAMP) {
66
+ try {
67
+ return stringToRuby(runtime, resultSet,
68
+ resultSet.getString(column));
69
+ } catch(java.io.IOException ioe) {
70
+ SQLException ex = new SQLException(ioe.getMessage());
71
+ throw (SQLException) ex.initCause(ioe);
72
+ }
73
+ }
74
+ return super.jdbcToRuby(runtime, column, type, resultSet);
75
+ }
76
+
52
77
  private static ObjectAllocator POSTGRESQL_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
53
78
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
54
79
  return new PostgresqlRubyJdbcConnection(runtime, klass);
@@ -1,11 +1,12 @@
1
1
  require 'jdbc/mysql'
2
2
 
3
3
  config = {
4
- :username => 'blog',
5
- :password => '',
4
+ # see db/mysql.rb
5
+ :username => 'arjdbc',
6
+ :password => 'arjdbc',
6
7
  :adapter => 'jdbc',
7
8
  :driver => 'com.mysql.jdbc.Driver',
8
- :url => 'jdbc:mysql://localhost:3306/weblog_development'
9
+ :url => 'jdbc:mysql://localhost:3306/arjdbc_test'
9
10
  }
10
11
 
11
12
  ActiveRecord::Base.establish_connection(config)
@@ -0,0 +1,8 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ require 'jdbc_common'
4
+ require 'db/db2'
5
+
6
+ class DB2ResetColumnInformationTest < Test::Unit::TestCase
7
+ include ResetColumnInformationTestMethods
8
+ end
@@ -0,0 +1,8 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ require 'jdbc_common'
4
+ require 'db/derby'
5
+
6
+ class DerbyResetColumnInformationTest < Test::Unit::TestCase
7
+ include ResetColumnInformationTestMethods
8
+ end
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ require 'jdbc_common'
4
+ require 'db/derby'
5
+
6
+ class DerbyRowLockingTest < Test::Unit::TestCase
7
+ include MigrationSetup
8
+ include RowLockingTestMethods
9
+ end
@@ -96,4 +96,44 @@ class DerbySimpleTest < Test::Unit::TestCase
96
96
  end
97
97
  end
98
98
  end
99
+
100
+ def test_data_types
101
+ # From test/models/data_types.rb, with the modifications as noted in the comments.
102
+ expected_types = [
103
+ ["id", :integer, { }],
104
+ ["sample_timestamp", :datetime, { }], # :timestamp is just an alias for :datetime in Derby
105
+ ["sample_datetime", :datetime, { }],
106
+ ["sample_date", :date, { }],
107
+ ["sample_time", :time, { }],
108
+ ["sample_decimal", :integer, { :precision => 15, :scale => 0 }], # it's an :integer because the :scale is 0 (...right?)
109
+ ["sample_small_decimal", :decimal, { :precision => 3, :scale => 2 }],
110
+ ["sample_default_decimal", :integer, { }], # decimal and integer are the same type in Derby
111
+ ["sample_float", :float, { }],
112
+ ["sample_binary", :binary, { }],
113
+ ["sample_boolean", :boolean, { }],
114
+ ["sample_string", :string, { :default => '' }],
115
+ ["sample_integer", :integer, { }], # don't care about the limit
116
+ ["sample_integer_with_limit_2", :integer, { }], # don't care about the limit
117
+ ["sample_integer_with_limit_8", :integer, { }], # don't care about the limit
118
+ ["sample_integer_no_limit", :integer, { }],
119
+ ["sample_integer_neg_default", :integer, { :default => -1 }],
120
+ ["sample_text", :text, { }],
121
+ ].sort{|a,b| a[0] <=> b[0]}
122
+
123
+ column_names = (expected_types.map{|et| et[0]} + DbType.column_names).sort.uniq
124
+ result = []
125
+ column_names.each do |column_name|
126
+ et = expected_types.detect{|t| t[0] == column_name }
127
+ col = DbType.columns_hash[column_name]
128
+ if col
129
+ attrs = et && Hash[et[2].keys.map{|k| [k, col.send(k)]}]
130
+ result << [col.name, col.type, attrs]
131
+ else
132
+ result << [column_name, nil, nil]
133
+ end
134
+ end
135
+ result.sort!{|a,b| a[0] <=> b[0]}
136
+
137
+ assert_equal expected_types, result
138
+ end
99
139
  end
@@ -15,8 +15,8 @@ class H2SchemaTest < Test::Unit::TestCase
15
15
  @connection.execute("set schema s2");
16
16
  CreateUsers.up
17
17
  @connection.execute("set schema public");
18
- Entry.set_table_name 's1.entries'
19
- User.set_table_name 's2.users'
18
+ Entry.table_name = 's1.entries'
19
+ User.table_name = 's2.users'
20
20
  user = User.create! :login => "something"
21
21
  Entry.create! :title => "title", :content => "content", :rating => 123.45, :user => user
22
22
  end
@@ -2,16 +2,29 @@ module Kernel
2
2
  def find_executable?(name)
3
3
  ENV['PATH'].split(File::PATH_SEPARATOR).detect {|p| File.executable?(File.join(p, name))}
4
4
  end
5
+
6
+ def have_postgres?
7
+ if find_executable?("psql")
8
+ if `psql -c '\\l' -U postgres 2>&1` && $?.exitstatus == 0
9
+ true
10
+ else
11
+ warn "No \"postgres\" role? You might need to execute `createuser postgres -drs' first."
12
+ false
13
+ end
14
+ end
15
+ end
5
16
  end
6
17
 
7
18
  # assert_queries and SQLCounter taken from rails active_record tests
8
19
  require 'test/unit'
9
20
  class Test::Unit::TestCase
10
- def assert_queries(num = 1)
21
+ def assert_queries(num = 1, matching = nil)
11
22
  ActiveRecord::SQLCounter.log = []
12
23
  yield
13
24
  ensure
14
- assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
25
+ queries = nil
26
+ ActiveRecord::SQLCounter.log.tap {|log| queries = (matching ? log.select {|s| s =~ matching } : log) }
27
+ assert_equal num, queries.size, "#{queries.size} instead of #{num} queries were executed.#{queries.size == 0 ? '' : "\nQueries:\n#{queries.join("\n")}"}"
15
28
  end
16
29
  end
17
30
 
@@ -16,6 +16,7 @@ require 'models/thing'
16
16
  require 'simple'
17
17
  require 'has_many_through'
18
18
  require 'helper'
19
+ require 'row_locking'
19
20
  require 'test/unit'
20
21
 
21
22
  # Comment/uncomment to enable logging to be loaded for any of the database adapters
@@ -6,32 +6,28 @@ begin
6
6
 
7
7
  class JndiConnectionPoolCallbacksTest < Test::Unit::TestCase
8
8
  def setup
9
- @connection = mock "JdbcConnection"
10
- @connection.stubs(:jndi_connection?).returns(true)
11
- @connection.stubs(:adapter=)
12
- @logger = mock "logger"
9
+ @logger = stub_everything "logger"
13
10
  @config = JNDI_CONFIG
11
+ @connection = ActiveRecord::ConnectionAdapters::JdbcConnection.new @config
14
12
  Entry.connection_pool.disconnect!
15
13
  assert !Entry.connection_pool.connected?
16
14
  class << Entry.connection_pool; public :instance_variable_set; end
17
15
  end
18
16
 
19
17
  def teardown
20
- @connection.stubs(:disconnect!)
21
18
  Entry.connection_pool.disconnect!
22
19
  end
23
20
 
24
21
  def test_should_call_hooks_on_checkout_and_checkin
25
- @connection.stubs(:active?).returns(true)
26
- @connection.expects(:disconnect!)
27
22
  @adapter = ActiveRecord::ConnectionAdapters::JdbcAdapter.new @connection, @logger, @config
28
23
  Entry.connection_pool.instance_variable_set "@connections", [@adapter]
24
+ assert !@connection.active?
29
25
 
30
- @connection.expects(:reconnect!)
31
26
  Entry.connection_pool.checkout
27
+ assert @connection.active?
32
28
 
33
- @connection.expects(:disconnect!)
34
29
  Entry.connection_pool.checkin @adapter
30
+ assert !@connection.active?
35
31
  end
36
32
  end
37
33
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env jruby
2
2
 
3
- if ARGV.length < 2
3
+ if ARGV.length < 2
4
4
  $stderr.puts "syntax: #{__FILE__} [filename] [configuration-name]"
5
5
  $stderr.puts " where filename points to a YAML database configuration file"
6
6
  $stderr.puts " and the configuration name is in this file"
@@ -21,7 +21,7 @@ ActiveRecord::Base.establish_connection(cfg)
21
21
  ActiveRecord::Schema.define do
22
22
  drop_table :authors rescue nil
23
23
  drop_table :author rescue nil
24
-
24
+
25
25
  create_table :author, :force => true do |t|
26
26
  t.column :name, :string, :null => false
27
27
  end
@@ -42,13 +42,13 @@ ActiveRecord::Schema.define do
42
42
  change_column_default :author, :female, false if /db2|derby|mssql|firebird/ !~ ARGV[1]
43
43
  remove_column :author, :died if /db2|derby/ !~ ARGV[1]
44
44
  rename_column :author, :wakeup_time, :waking_time if /db2|derby|mimer/ !~ ARGV[1]
45
-
45
+
46
46
  add_index :author, :name, :unique if /db2/ !~ ARGV[1]
47
47
  add_index :author, [:age,:female], :name => :is_age_female if /db2/ !~ ARGV[1]
48
-
48
+
49
49
  remove_index :author, :name if /db2/ !~ ARGV[1]
50
50
  remove_index :author, :name => :is_age_female if /db2/ !~ ARGV[1]
51
-
51
+
52
52
  rename_table :author, :authors if /db2|firebird|mimer/ !~ ARGV[1]
53
53
 
54
54
 
@@ -73,7 +73,7 @@ ActiveRecord::Schema.define do
73
73
  end
74
74
 
75
75
  class Author < ActiveRecord::Base;
76
- set_table_name "author" if /db2|firebird|mimer/ =~ ARGV[1]
76
+ self.table_name = "author" if /db2|firebird|mimer/ =~ ARGV[1]
77
77
  end
78
78
 
79
79
  class Order < ActiveRecord::Base
@@ -83,7 +83,7 @@ end
83
83
  class Product < ActiveRecord::Base
84
84
  has_many :orders, :through => :line_items
85
85
  has_many :line_items
86
-
86
+
87
87
  def self.find_products_for_sale
88
88
  find(:all, :order => "title")
89
89
  end
@@ -95,51 +95,51 @@ class LineItem < ActiveRecord::Base
95
95
  end
96
96
 
97
97
  Product.create(:title => 'Pragmatic Project Automation',
98
- :description =>
98
+ :description =>
99
99
  %{<p>
100
- <em>Pragmatic Project Automation</em> shows you how to improve the
101
- consistency and repeatability of your project's procedures using
100
+ <em>Pragmatic Project Automation</em> shows you how to improve the
101
+ consistency and repeatability of your project's procedures using
102
102
  automation to reduce risk and errors.
103
103
  </p>
104
104
  <p>
105
- Simply put, we're going to put this thing called a computer to work
106
- for you doing the mundane (but important) project stuff. That means
107
- you'll have more time and energy to do the really
105
+ Simply put, we're going to put this thing called a computer to work
106
+ for you doing the mundane (but important) project stuff. That means
107
+ you'll have more time and energy to do the really
108
108
  exciting---and difficult---stuff, like writing quality code.
109
109
  </p>},
110
- :image_url => '/images/auto.jpg',
110
+ :image_url => '/images/auto.jpg',
111
111
  :price => 29.95)
112
112
 
113
113
 
114
114
  Product.create(:title => 'Pragmatic Version Control',
115
115
  :description =>
116
116
  %{<p>
117
- This book is a recipe-based approach to using Subversion that will
118
- get you up and
119
- running quickly---and correctly. All projects need version control:
120
- it's a foundational piece of any project's infrastructure. Yet half
121
- of all project teams in the U.S. don't use any version control at all.
117
+ This book is a recipe-based approach to using Subversion that will
118
+ get you up and
119
+ running quickly---and correctly. All projects need version control:
120
+ it's a foundational piece of any project's infrastructure. Yet half
121
+ of all project teams in the U.S. don't use any version control at all.
122
122
  Many others don't use it well, and end up experiencing time-consuming problems.
123
123
  </p>},
124
124
  :image_url => '/images/svn.jpg',
125
125
  :price => 28.50)
126
-
126
+
127
127
  # . . .
128
128
 
129
129
 
130
130
  Product.create(:title => 'Pragmatic Unit Testing (C#)',
131
- :description =>
131
+ :description =>
132
132
  %{<p>
133
- Pragmatic programmers use feedback to drive their development and
134
- personal processes. The most valuable feedback you can get while
133
+ Pragmatic programmers use feedback to drive their development and
134
+ personal processes. The most valuable feedback you can get while
135
135
  coding comes from unit testing.
136
136
  </p>
137
137
  <p>
138
- Without good tests in place, coding can become a frustrating game of
139
- "whack-a-mole." That's the carnival game where the player strikes at a
140
- mechanical mole; it retreats and another mole pops up on the opposite side
141
- of the field. The moles pop up and down so fast that you end up flailing
142
- your mallet helplessly as the moles continue to pop up where you least
138
+ Without good tests in place, coding can become a frustrating game of
139
+ "whack-a-mole." That's the carnival game where the player strikes at a
140
+ mechanical mole; it retreats and another mole pops up on the opposite side
141
+ of the field. The moles pop up and down so fast that you end up flailing
142
+ your mallet helplessly as the moles continue to pop up where you least
143
143
  expect them.
144
144
  </p>},
145
145
  :image_url => '/images/utc.jpg',
@@ -148,7 +148,7 @@ end
148
148
 
149
149
 
150
150
 
151
- 1.times do
151
+ 1.times do
152
152
  $stderr.print '.'
153
153
  Author.destroy_all
154
154
  Author.create(:name => "Arne Svensson", :age => 30)
@@ -181,11 +181,11 @@ end
181
181
  puts "order: #{order.line_items.inspect}, with id: #{order.id} and name: #{order.name}"
182
182
  end
183
183
 
184
- ActiveRecord::Schema.define do
184
+ ActiveRecord::Schema.define do
185
185
  drop_table :line_items
186
186
  drop_table :orders
187
187
  drop_table :products
188
188
 
189
-
189
+
190
190
  drop_table((/db2|firebird|mimer/=~ARGV[1]? :author : :authors ))
191
191
  end