activerecord 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (55) hide show
  1. data/CHANGELOG +98 -0
  2. data/install.rb +1 -0
  3. data/lib/active_record.rb +1 -0
  4. data/lib/active_record/acts/list.rb +19 -16
  5. data/lib/active_record/associations.rb +164 -164
  6. data/lib/active_record/associations/association_collection.rb +44 -71
  7. data/lib/active_record/associations/association_proxy.rb +76 -0
  8. data/lib/active_record/associations/belongs_to_association.rb +74 -0
  9. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +34 -21
  10. data/lib/active_record/associations/has_many_association.rb +34 -30
  11. data/lib/active_record/associations/has_one_association.rb +48 -0
  12. data/lib/active_record/base.rb +62 -18
  13. data/lib/active_record/callbacks.rb +17 -8
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +11 -10
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  16. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -1
  17. data/lib/active_record/connection_adapters/sqlite_adapter.rb +94 -73
  18. data/lib/active_record/deprecated_associations.rb +46 -8
  19. data/lib/active_record/fixtures.rb +1 -1
  20. data/lib/active_record/observer.rb +5 -1
  21. data/lib/active_record/support/binding_of_caller.rb +72 -68
  22. data/lib/active_record/support/breakpoint.rb +526 -524
  23. data/lib/active_record/support/class_inheritable_attributes.rb +105 -29
  24. data/lib/active_record/support/core_ext.rb +1 -0
  25. data/lib/active_record/support/core_ext/hash.rb +5 -0
  26. data/lib/active_record/support/core_ext/hash/keys.rb +35 -0
  27. data/lib/active_record/support/core_ext/numeric.rb +7 -0
  28. data/lib/active_record/support/core_ext/numeric/bytes.rb +33 -0
  29. data/lib/active_record/support/core_ext/numeric/time.rb +59 -0
  30. data/lib/active_record/support/core_ext/string.rb +5 -0
  31. data/lib/active_record/support/core_ext/string/inflections.rb +41 -0
  32. data/lib/active_record/support/dependencies.rb +1 -14
  33. data/lib/active_record/support/inflector.rb +6 -6
  34. data/lib/active_record/support/misc.rb +0 -24
  35. data/lib/active_record/validations.rb +34 -1
  36. data/lib/active_record/vendor/mysql411.rb +305 -0
  37. data/rakefile +11 -2
  38. data/test/abstract_unit.rb +1 -2
  39. data/test/associations_test.rb +234 -23
  40. data/test/base_test.rb +50 -1
  41. data/test/callbacks_test.rb +16 -0
  42. data/test/connections/native_mysql/connection.rb +2 -2
  43. data/test/connections/native_sqlite3/connection.rb +34 -0
  44. data/test/deprecated_associations_test.rb +36 -2
  45. data/test/fixtures/company.rb +2 -0
  46. data/test/fixtures/computer.rb +3 -0
  47. data/test/fixtures/computers.yml +3 -0
  48. data/test/fixtures/db_definitions/db2.sql +5 -0
  49. data/test/fixtures/db_definitions/mysql.sql +5 -0
  50. data/test/fixtures/db_definitions/postgresql.sql +5 -0
  51. data/test/fixtures/db_definitions/sqlite.sql +5 -0
  52. data/test/fixtures/db_definitions/sqlserver.sql +5 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/validations_test.rb +21 -0
  55. metadata +22 -2
@@ -1,33 +1,67 @@
1
1
  # sqlite_adapter.rb
2
- # author: Luke Holden <lholden@cablelan.net>
2
+ # author: Luke Holden <lholden@cablelan.net>
3
+ # updated for SQLite3: Jamis Buck <jamis_buck@byu.edu>
3
4
 
4
5
  require 'active_record/connection_adapters/abstract_adapter'
5
6
 
6
7
  module ActiveRecord
7
8
  class Base
8
- # Establishes a connection to the database that's used by all Active Record objects
9
- def self.sqlite_connection(config) # :nodoc:
10
- require_library_or_gem('sqlite') unless self.class.const_defined?(:SQLite)
11
- symbolize_strings_in_hash(config)
12
- unless config.has_key?(:dbfile)
13
- raise ArgumentError, "No database file specified. Missing argument: dbfile"
9
+ class << self
10
+ # sqlite3 adapter reuses sqlite_connection.
11
+ def sqlite3_connection(config) # :nodoc:
12
+ parse_config!(config)
13
+
14
+ unless self.class.const_defined?(:SQLite3)
15
+ require_library_or_gem(config[:adapter])
16
+ end
17
+
18
+ db = SQLite3::Database.new(
19
+ config[:dbfile],
20
+ :results_as_hash => true,
21
+ :type_translation => false
22
+ )
23
+ ConnectionAdapters::SQLiteAdapter.new(db, logger)
24
+ end
25
+
26
+ # Establishes a connection to the database that's used by all Active Record objects
27
+ def sqlite_connection(config) # :nodoc:
28
+ parse_config!(config)
29
+
30
+ unless self.class.const_defined?(:SQLite)
31
+ require_library_or_gem(config[:adapter])
32
+
33
+ db = SQLite::Database.new(config[:dbfile], 0)
34
+ db.show_datatypes = "ON" if !defined? SQLite::Version
35
+ db.results_as_hash = true if defined? SQLite::Version
36
+ db.type_translation = false
37
+
38
+ # "Downgrade" deprecated sqlite API
39
+ if SQLite.const_defined?(:Version)
40
+ ConnectionAdapters::SQLiteAdapter.new(db, logger)
41
+ else
42
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
43
+ end
44
+ end
14
45
  end
15
-
16
- config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT) if Object.const_defined?(:RAILS_ROOT)
17
- db = SQLite::Database.new(config[:dbfile], 0)
18
46
 
19
- db.show_datatypes = "ON" if !defined? SQLite::Version
20
- db.results_as_hash = true if defined? SQLite::Version
21
- db.type_translation = false
47
+ private
48
+ def parse_config!(config)
49
+ # Require dbfile.
50
+ unless config.has_key?(:dbfile)
51
+ raise ArgumentError, "No database file specified. Missing argument: dbfile"
52
+ end
22
53
 
23
- ConnectionAdapters::SQLiteAdapter.new(db, logger)
54
+ # Allow database path relative to RAILS_ROOT.
55
+ if Object.const_defined?(:RAILS_ROOT)
56
+ config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT)
57
+ end
58
+ end
24
59
  end
25
60
  end
26
61
 
27
62
  module ConnectionAdapters
28
63
 
29
64
  class SQLiteColumn < Column
30
-
31
65
  def string_to_binary(value)
32
66
  value.gsub(/(\0|\%)/) do
33
67
  case $1
@@ -45,92 +79,79 @@ module ActiveRecord
45
79
  end
46
80
  end
47
81
  end
48
-
49
82
  end
83
+
50
84
  class SQLiteAdapter < AbstractAdapter # :nodoc:
51
- def select_all(sql, name = nil)
52
- select(sql, name)
85
+ def execute(sql, name = nil)
86
+ log(sql, name) { @connection.execute(sql) }
53
87
  end
54
88
 
55
- def select_one(sql, name = nil)
56
- result = select(sql, name)
57
- result.nil? ? nil : result.first
89
+ def update(sql, name = nil)
90
+ execute(sql, name)
91
+ @connection.changes
58
92
  end
59
93
 
60
- def columns(table_name, name = nil)
61
- table_structure(table_name).inject([]) do |columns, field|
62
- columns << SQLiteColumn.new(field['name'], field['dflt_value'], field['type'])
63
- columns
64
- end
94
+ def delete(sql, name = nil)
95
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
96
+ execute(sql, name)
97
+ @connection.changes
65
98
  end
66
99
 
67
100
  def insert(sql, name = nil, pk = nil, id_value = nil)
68
101
  execute(sql, name = nil)
69
- id_value || @connection.send( defined?( SQLite::Version ) ? :last_insert_row_id : :last_insert_rowid )
102
+ id_value || @connection.last_insert_row_id
70
103
  end
71
104
 
72
- def execute(sql, name = nil)
73
- log(sql, name, @connection) do |connection|
74
- if defined?( SQLite::Version )
75
- case sql
76
- when "BEGIN" then connection.transaction
77
- when "COMMIT" then connection.commit
78
- when "ROLLBACK" then connection.rollback
79
- else connection.execute(sql)
80
- end
81
- else
82
- connection.execute( sql )
105
+ def select_all(sql, name = nil)
106
+ execute(sql, name).map do |row|
107
+ record = {}
108
+ row.each_key do |key|
109
+ record[key.sub(/\w+\./, '')] = row[key] unless key.is_a?(Fixnum)
83
110
  end
111
+ record
84
112
  end
85
113
  end
86
114
 
87
- def update(sql, name = nil)
88
- execute(sql, name)
89
- @connection.changes
115
+ def select_one(sql, name = nil)
116
+ result = select_all(sql, name)
117
+ result.nil? ? nil : result.first
90
118
  end
91
-
92
- def delete(sql, name = nil)
93
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
94
- execute(sql, name)
95
- @connection.changes
119
+
120
+
121
+ def begin_db_transaction() @connection.transaction end
122
+ def commit_db_transaction() @connection.commit end
123
+ def rollback_db_transaction() @connection.rollback end
124
+
125
+
126
+ def tables
127
+ execute('.table').map { |table| Table.new(table) }
96
128
  end
97
129
 
98
- def begin_db_transaction() execute "BEGIN" end
99
- def commit_db_transaction() execute "COMMIT" end
100
- def rollback_db_transaction() execute "ROLLBACK" end
130
+ def columns(table_name, name = nil)
131
+ table_structure(table_name).map { |field|
132
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'])
133
+ }
134
+ end
101
135
 
102
136
  def quote_string(s)
103
- SQLite::Database.quote(s)
137
+ @connection.class.quote(s)
104
138
  end
105
-
139
+
106
140
  def quote_column_name(name)
107
141
  return "'#{name}'"
108
142
  end
109
143
 
110
- private
111
- def select(sql, name = nil)
112
- results = nil
113
- log(sql, name, @connection) { |connection| results = connection.execute(sql) }
114
-
115
- rows = []
116
-
117
- results.each do |row|
118
- hash_only_row = {}
119
- row.each_key do |key|
120
- hash_only_row[key.sub(/\w+\./, "")] = row[key] unless key.class == Fixnum
121
- end
122
- rows << hash_only_row
123
- end
124
-
125
- return rows
126
- end
127
-
144
+ protected
128
145
  def table_structure(table_name)
129
- sql = "PRAGMA table_info(#{table_name});"
130
- results = nil
131
- log(sql, nil, @connection) { |connection| results = connection.execute(sql) }
132
- return results
146
+ execute "PRAGMA table_info(#{table_name})"
133
147
  end
134
148
  end
149
+
150
+ class DeprecatedSQLiteAdapter < SQLiteAdapter # :nodoc:
151
+ def insert(sql, name = nil, pk = nil, id_value = nil)
152
+ execute(sql, name = nil)
153
+ id_value || @connection.last_insert_rowid
154
+ end
155
+ end
135
156
  end
136
157
  end
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  end_eval
19
19
  end
20
20
 
21
- def deprecated_remove_association_relation(association_name)# :nodoc:
21
+ def deprecated_remove_association_relation(association_name)# :nodoc:
22
22
  module_eval <<-"end_eval", __FILE__, __LINE__
23
23
  def remove_#{association_name}(*items)
24
24
  #{association_name}.delete(items)
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
  end_eval
51
51
  end
52
52
 
53
- def deprecated_create_method(collection_name)# :nodoc:
53
+ def deprecated_collection_create_method(collection_name)# :nodoc:
54
54
  module_eval <<-"end_eval", __FILE__, __LINE__
55
55
  def create_in_#{collection_name}(attributes = {})
56
56
  #{collection_name}.create(attributes)
@@ -58,13 +58,51 @@ module ActiveRecord
58
58
  end_eval
59
59
  end
60
60
 
61
- def deprecated_build_method(collection_name)# :nodoc:
62
- module_eval <<-"end_eval", __FILE__, __LINE__
63
- def build_to_#{collection_name}(attributes = {})
64
- #{collection_name}.build(attributes)
61
+ def deprecated_collection_build_method(collection_name)# :nodoc:
62
+ module_eval <<-"end_eval", __FILE__, __LINE__
63
+ def build_to_#{collection_name}(attributes = {})
64
+ #{collection_name}.build(attributes)
65
+ end
66
+ end_eval
67
+ end
68
+
69
+ def deprecated_association_comparison_method(association_name, association_class_name)
70
+ module_eval <<-"end_eval", __FILE__, __LINE__
71
+ def #{association_name}?(comparison_object, force_reload = false)
72
+ if comparison_object.kind_of?(#{association_class_name})
73
+ #{association_name}(force_reload) == comparison_object
74
+ else
75
+ raise "Comparison object is a #{association_class_name}, should have been \#{comparison_object.class.name}"
65
76
  end
66
- end_eval
67
- end
77
+ end
78
+ end_eval
79
+ end
80
+
81
+ def deprecated_has_association_method(association_name)
82
+ module_eval <<-"end_eval", __FILE__, __LINE__
83
+ def has_#{association_name}?(force_reload = false)
84
+ !#{association_name}(force_reload).nil?
85
+ end
86
+ end_eval
87
+ end
88
+
89
+ def deprecated_build_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
90
+ module_eval <<-"end_eval", __FILE__, __LINE__
91
+ def #{method_prefix + collection_name}(attributes = {})
92
+ association = #{collection_class_name}.new
93
+ association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
94
+ association
95
+ end
96
+ end_eval
97
+ end
98
+
99
+ def deprecated_create_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
100
+ module_eval <<-"end_eval", __FILE__, __LINE__
101
+ def #{method_prefix + collection_name}(attributes = nil)
102
+ #{collection_class_name}.create((attributes || {}).merge({ "#{class_primary_key_name}" => id}))
103
+ end
104
+ end_eval
105
+ end
68
106
  end
69
107
  end
70
108
  end
@@ -158,7 +158,7 @@ class Fixtures < Hash
158
158
  ActiveRecord::Base.logger.level = Logger::ERROR
159
159
 
160
160
  fixtures = table_names.flatten.map do |table_name|
161
- Fixtures.new(connection, table_name.to_s, File.join(fixtures_directory, table_name.to_s))
161
+ Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
162
162
  end
163
163
 
164
164
  connection.transaction do
@@ -43,7 +43,11 @@ module ActiveRecord
43
43
  # The observer can implement callback methods for each of the methods described in the Callbacks module.
44
44
  class Observer
45
45
  include Singleton
46
-
46
+
47
+ def self.observe(*models)
48
+ define_method(:observed_class) { models }
49
+ end
50
+
47
51
  def initialize
48
52
  [ observed_class ].flatten.each do |klass|
49
53
  klass.add_observer(self)
@@ -1,81 +1,85 @@
1
1
  begin
2
2
  require 'simplecc'
3
3
  rescue LoadError
4
- def Continuation.create(*args, &block)
5
- cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
6
- result ||= args
7
- return *[cc, *result]
4
+ class Continuation #:nodoc:
5
+ def create(*args, &block)
6
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
7
+ result ||= args
8
+ return *[cc, *result]
9
+ end
8
10
  end
9
11
  end
10
12
 
11
- # This method returns the binding of the method that called your
12
- # method. It will raise an Exception when you're not inside a method.
13
- #
14
- # It's used like this:
15
- # def inc_counter(amount = 1)
16
- # Binding.of_caller do |binding|
17
- # # Create a lambda that will increase the variable 'counter'
18
- # # in the caller of this method when called.
19
- # inc = eval("lambda { |arg| counter += arg }", binding)
20
- # # We can refer to amount from inside this block safely.
21
- # inc.call(amount)
22
- # end
23
- # # No other statements can go here. Put them inside the block.
24
- # end
25
- # counter = 0
26
- # 2.times { inc_counter }
27
- # counter # => 2
28
- #
29
- # Binding.of_caller must be the last statement in the method.
30
- # This means that you will have to put everything you want to
31
- # do after the call to Binding.of_caller into the block of it.
32
- # This should be no problem however, because Ruby has closures.
33
- # If you don't do this an Exception will be raised. Because of
34
- # the way that Binding.of_caller is implemented it has to be
35
- # done this way.
36
- def Binding.of_caller(&block)
37
- old_critical = Thread.critical
38
- Thread.critical = true
39
- count = 0
40
- cc, result, error, extra_data = Continuation.create(nil, nil)
41
- error.call if error
13
+ class Binding #:nodoc:
14
+ # This method returns the binding of the method that called your
15
+ # method. It will raise an Exception when you're not inside a method.
16
+ #
17
+ # It's used like this:
18
+ # def inc_counter(amount = 1)
19
+ # Binding.of_caller do |binding|
20
+ # # Create a lambda that will increase the variable 'counter'
21
+ # # in the caller of this method when called.
22
+ # inc = eval("lambda { |arg| counter += arg }", binding)
23
+ # # We can refer to amount from inside this block safely.
24
+ # inc.call(amount)
25
+ # end
26
+ # # No other statements can go here. Put them inside the block.
27
+ # end
28
+ # counter = 0
29
+ # 2.times { inc_counter }
30
+ # counter # => 2
31
+ #
32
+ # Binding.of_caller must be the last statement in the method.
33
+ # This means that you will have to put everything you want to
34
+ # do after the call to Binding.of_caller into the block of it.
35
+ # This should be no problem however, because Ruby has closures.
36
+ # If you don't do this an Exception will be raised. Because of
37
+ # the way that Binding.of_caller is implemented it has to be
38
+ # done this way.
39
+ def of_caller(&block)
40
+ old_critical = Thread.critical
41
+ Thread.critical = true
42
+ count = 0
43
+ cc, result, error, extra_data = Continuation.create(nil, nil)
44
+ error.call if error
42
45
 
43
- tracer = lambda do |*args|
44
- type, context, extra_data = args[0], args[4], args
45
- if type == "return"
46
- count += 1
47
- # First this method and then calling one will return --
48
- # the trace event of the second event gets the context
49
- # of the method which called the method that called this
50
- # method.
51
- if count == 2
52
- # It would be nice if we could restore the trace_func
53
- # that was set before we swapped in our own one, but
54
- # this is impossible without overloading set_trace_func
55
- # in current Ruby.
46
+ tracer = lambda do |*args|
47
+ type, context, extra_data = args[0], args[4], args
48
+ if type == "return"
49
+ count += 1
50
+ # First this method and then calling one will return --
51
+ # the trace event of the second event gets the context
52
+ # of the method which called the method that called this
53
+ # method.
54
+ if count == 2
55
+ # It would be nice if we could restore the trace_func
56
+ # that was set before we swapped in our own one, but
57
+ # this is impossible without overloading set_trace_func
58
+ # in current Ruby.
59
+ set_trace_func(nil)
60
+ cc.call(eval("binding", context), nil, extra_data)
61
+ end
62
+ elsif type == "line" then
63
+ nil
64
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
65
+ nil
66
+ else
56
67
  set_trace_func(nil)
57
- cc.call(eval("binding", context), nil, extra_data)
68
+ error_msg = "Binding.of_caller used in non-method context or " +
69
+ "trailing statements of method using it aren't in the block."
70
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
58
71
  end
59
- elsif type == "line" then
60
- nil
61
- elsif type == "c-return" and extra_data[3] == :set_trace_func then
62
- nil
63
- else
64
- set_trace_func(nil)
65
- error_msg = "Binding.of_caller used in non-method context or " +
66
- "trailing statements of method using it aren't in the block."
67
- cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
68
72
  end
69
- end
70
73
 
71
- unless result
72
- set_trace_func(tracer)
73
- return nil
74
- else
75
- Thread.critical = old_critical
76
- case block.arity
77
- when 1 then yield(result)
78
- else yield(result, extra_data)
74
+ unless result
75
+ set_trace_func(tracer)
76
+ return nil
77
+ else
78
+ Thread.critical = old_critical
79
+ case block.arity
80
+ when 1 then yield(result)
81
+ else yield(result, extra_data)
82
+ end
79
83
  end
80
84
  end
81
- end
85
+ end