activerecord 1.12.2 → 1.13.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 (66) hide show
  1. data/CHANGELOG +92 -0
  2. data/README +9 -9
  3. data/lib/active_record/acts/list.rb +1 -1
  4. data/lib/active_record/acts/nested_set.rb +13 -13
  5. data/lib/active_record/acts/tree.rb +7 -6
  6. data/lib/active_record/aggregations.rb +4 -4
  7. data/lib/active_record/associations.rb +82 -21
  8. data/lib/active_record/associations/association_collection.rb +0 -8
  9. data/lib/active_record/associations/association_proxy.rb +5 -2
  10. data/lib/active_record/associations/belongs_to_association.rb +6 -2
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
  12. data/lib/active_record/associations/has_many_association.rb +34 -5
  13. data/lib/active_record/associations/has_one_association.rb +1 -1
  14. data/lib/active_record/base.rb +144 -59
  15. data/lib/active_record/callbacks.rb +6 -6
  16. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
  18. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
  20. data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
  21. data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
  24. data/lib/active_record/fixtures.rb +10 -8
  25. data/lib/active_record/migration.rb +20 -6
  26. data/lib/active_record/observer.rb +4 -3
  27. data/lib/active_record/reflection.rb +5 -5
  28. data/lib/active_record/transactions.rb +2 -2
  29. data/lib/active_record/validations.rb +70 -40
  30. data/lib/active_record/vendor/mysql411.rb +9 -13
  31. data/lib/active_record/version.rb +2 -2
  32. data/rakefile +1 -1
  33. data/test/abstract_unit.rb +5 -0
  34. data/test/ar_schema_test.rb +1 -1
  35. data/test/associations_extensions_test.rb +37 -0
  36. data/test/associations_go_eager_test.rb +25 -0
  37. data/test/associations_test.rb +14 -6
  38. data/test/base_test.rb +63 -45
  39. data/test/connections/native_sqlite/connection.rb +2 -2
  40. data/test/connections/native_sqlite3/connection.rb +2 -2
  41. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  42. data/test/debug.log +2857 -0
  43. data/test/deprecated_finder_test.rb +3 -9
  44. data/test/finder_test.rb +27 -13
  45. data/test/fixtures/author.rb +4 -0
  46. data/test/fixtures/comment.rb +4 -8
  47. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
  48. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
  49. data/test/fixtures/db_definitions/db2.drop.sql +0 -1
  50. data/test/fixtures/db_definitions/oci.sql +2 -0
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
  52. data/test/fixtures/developer.rb +18 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/fixtures/fixture_database_2.sqlite +0 -0
  55. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  56. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  57. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  58. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  59. data/test/fixtures/post.rb +18 -4
  60. data/test/fixtures/reply.rb +3 -1
  61. data/test/inheritance_test.rb +3 -2
  62. data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
  63. data/test/migration_test.rb +68 -31
  64. data/test/readonly_test.rb +65 -5
  65. data/test/validations_test.rb +10 -2
  66. metadata +13 -4
@@ -124,14 +124,6 @@ module ActiveRecord
124
124
  end
125
125
 
126
126
  private
127
- def method_missing(method, *args, &block)
128
- if @target.respond_to?(method) or (not @association_class.respond_to?(method) and Class.respond_to?(method))
129
- super
130
- else
131
- @association_class.constrain(:conditions => @finder_sql, :joins => @join_sql) { @association_class.send(method, *args, &block) }
132
- end
133
- end
134
-
135
127
  def raise_on_type_mismatch(record)
136
128
  raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
137
129
  end
@@ -2,7 +2,8 @@ module ActiveRecord
2
2
  module Associations
3
3
  class AssociationProxy #:nodoc:
4
4
  alias_method :proxy_respond_to?, :respond_to?
5
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^send)/ }
5
+ alias_method :proxy_extend, :extend
6
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }
6
7
 
7
8
  def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
8
9
  @owner = owner
@@ -11,6 +12,8 @@ module ActiveRecord
11
12
  @association_class = eval(association_class_name, nil, __FILE__, __LINE__)
12
13
  @association_class_primary_key_name = association_class_primary_key_name
13
14
 
15
+ proxy_extend(options[:extend]) if options[:extend]
16
+
14
17
  reset
15
18
  end
16
19
 
@@ -95,4 +98,4 @@ module ActiveRecord
95
98
  end
96
99
  end
97
100
  end
98
- end
101
+ end
@@ -49,9 +49,13 @@ module ActiveRecord
49
49
  private
50
50
  def find_target
51
51
  if @options[:conditions]
52
- @association_class.find_on_conditions(@owner[@association_class_primary_key_name], interpolate_sql(@options[:conditions]))
52
+ @association_class.find(
53
+ @owner[@association_class_primary_key_name],
54
+ :conditions => interpolate_sql(@options[:conditions]),
55
+ :include => @options[:include]
56
+ )
53
57
  else
54
- @association_class.find(@owner[@association_class_primary_key_name])
58
+ @association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
55
59
  end
56
60
  end
57
61
 
@@ -76,11 +76,21 @@ module ActiveRecord
76
76
  end
77
77
 
78
78
  protected
79
+ def method_missing(method, *args, &block)
80
+ if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
81
+ super
82
+ else
83
+ @association_class.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
84
+ @association_class.send(method, *args, &block)
85
+ end
86
+ end
87
+ end
88
+
79
89
  def find_target
80
90
  if @options[:finder_sql]
81
91
  records = @association_class.find_by_sql(@finder_sql)
82
92
  else
83
- records = find(:all)
93
+ records = find(:all, :include => @options[:include])
84
94
  end
85
95
 
86
96
  @options[:uniq] ? uniq(records) : records
@@ -23,12 +23,12 @@ module ActiveRecord
23
23
  # DEPRECATED.
24
24
  def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
25
25
  if @options[:finder_sql]
26
- records = @association_class.find_by_sql(@finder_sql)
26
+ @association_class.find_by_sql(@finder_sql)
27
27
  else
28
- sql = @finder_sql
29
- sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
28
+ conditions = @finder_sql
29
+ conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
30
30
  orderings ||= @options[:order]
31
- records = @association_class.find_all(sql, orderings, limit, joins)
31
+ @association_class.find_all(conditions, orderings, limit, joins)
32
32
  end
33
33
  end
34
34
 
@@ -85,8 +85,37 @@ module ActiveRecord
85
85
  end
86
86
 
87
87
  protected
88
+ def method_missing(method, *args, &block)
89
+ if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
90
+ super
91
+ else
92
+ @association_class.with_scope(
93
+ :find => {
94
+ :conditions => @finder_sql,
95
+ :joins => @join_sql,
96
+ :readonly => false
97
+ },
98
+ :create => {
99
+ @association_class_primary_key_name => @owner.id
100
+ }
101
+ ) do
102
+ @association_class.send(method, *args, &block)
103
+ end
104
+ end
105
+ end
106
+
88
107
  def find_target
89
- find_all
108
+ if @options[:finder_sql]
109
+ @association_class.find_by_sql(@finder_sql)
110
+ else
111
+ @association_class.find(:all,
112
+ :conditions => @finder_sql,
113
+ :order => @options[:order],
114
+ :limit => @options[:limit],
115
+ :joins => @options[:joins],
116
+ :include => @options[:include]
117
+ )
118
+ end
90
119
  end
91
120
 
92
121
  def count_records
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
 
58
58
  private
59
59
  def find_target
60
- @association_class.find(:first, :conditions => @finder_sql, :order => @options[:order])
60
+ @association_class.find(:first, :conditions => @finder_sql, :order => @options[:order], :include => @options[:include])
61
61
  end
62
62
 
63
63
  def target_obsolete?
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'set'
2
3
  require 'active_record/deprecated_finders'
3
4
 
4
5
  module ActiveRecord #:nodoc:
@@ -139,7 +140,7 @@ module ActiveRecord #:nodoc:
139
140
  #
140
141
  # == Dynamic attribute-based finders
141
142
  #
142
- # Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
143
+ # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
143
144
  # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
144
145
  # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
145
146
  # <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
@@ -154,6 +155,15 @@ module ActiveRecord #:nodoc:
154
155
  # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
155
156
  # actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
156
157
  #
158
+ # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
159
+ # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Example:
160
+ #
161
+ # # No 'Summer' tag exists
162
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
163
+ #
164
+ # # Now the 'Summer' tag does exist
165
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
166
+ #
157
167
  # == Saving arrays, hashes, and other non-mappable objects in text columns
158
168
  #
159
169
  # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
@@ -384,9 +394,14 @@ module ActiveRecord #:nodoc:
384
394
  def find(*args)
385
395
  options = extract_options_from_args!(args)
386
396
 
387
- # :joins implies :readonly => true if unset.
388
- if options[:joins] and !options.has_key?(:readonly)
389
- options[:readonly] = true
397
+ # Inherit :readonly from finder scope if set. Otherwise,
398
+ # if :joins is not blank then :readonly defaults to true.
399
+ unless options.has_key?(:readonly)
400
+ if scoped?(:find, :readonly)
401
+ options[:readonly] = scope(:find, :readonly)
402
+ elsif !options[:joins].blank?
403
+ options[:readonly] = true
404
+ end
390
405
  end
391
406
 
392
407
  case args.first
@@ -445,6 +460,8 @@ module ActiveRecord #:nodoc:
445
460
  if attributes.is_a?(Array)
446
461
  attributes.collect { |attr| create(attr) }
447
462
  else
463
+ attributes.reverse_merge!(scope(:create)) if scoped?(:create)
464
+
448
465
  object = new(attributes)
449
466
  object.save
450
467
  object
@@ -702,6 +719,22 @@ module ActiveRecord #:nodoc:
702
719
  class_name
703
720
  end
704
721
 
722
+ # Indicates whether the table associated with this class exists
723
+ def table_exists?
724
+ if connection.respond_to?(:tables)
725
+ connection.tables.include? table_name
726
+ else
727
+ # if the connection adapter hasn't implemented tables, there are two crude tests that can be
728
+ # used - see if getting column info raises an error, or if the number of columns returned is zero
729
+ begin
730
+ reset_column_information
731
+ columns.size > 0
732
+ rescue ActiveRecord::StatementInvalid
733
+ false
734
+ end
735
+ end
736
+ end
737
+
705
738
  # Returns an array of column objects for the table associated with this class.
706
739
  def columns
707
740
  unless @columns
@@ -740,15 +773,14 @@ module ActiveRecord #:nodoc:
740
773
  end
741
774
  end
742
775
 
743
-
744
776
  # Contains the names of the generated reader methods.
745
777
  def read_methods
746
- @read_methods ||= {}
778
+ @read_methods ||= Set.new
747
779
  end
748
-
780
+
749
781
  # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
750
782
  def reset_column_information
751
- read_methods.each_key {|name| undef_method(name)}
783
+ read_methods.each { |name| undef_method(name) }
752
784
  @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
753
785
  end
754
786
 
@@ -807,20 +839,37 @@ module ActiveRecord #:nodoc:
807
839
  ensure
808
840
  logger.level = old_logger_level if logger
809
841
  end
810
-
811
- # Add constraints to all queries to the same model in the given block.
812
- # Currently supported constraints are <tt>:conditions</tt> and <tt>:joins</tt>
842
+
843
+ # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
844
+ # method_name may be :find or :create.
845
+ # :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
846
+ # <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options.
847
+ # :create parameters are an attributes hash.
813
848
  #
814
- # Article.constrain(:conditions => "blog_id = 1") do
849
+ # Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
815
850
  # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
851
+ # a = Article.create(1)
852
+ # a.blog_id == 1
816
853
  # end
817
- def constrain(options = {}, &block)
818
- begin
819
- self.scope_constraints = options
820
- block.call if block_given?
821
- ensure
822
- self.scope_constraints = nil
854
+ def with_scope(method_scoping = {})
855
+ # Dup first and second level of hash (method and params).
856
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
857
+ hash[method] = params.dup
858
+ hash
859
+ end
860
+
861
+ method_scoping.assert_valid_keys [:find, :create]
862
+ if f = method_scoping[:find]
863
+ f.assert_valid_keys [:conditions, :joins, :offset, :limit, :readonly]
864
+ f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
823
865
  end
866
+
867
+ raise ArgumentError, "Nested scopes are not yet supported: #{scoped_methods.inspect}" unless scoped_methods.nil?
868
+
869
+ self.scoped_methods = method_scoping
870
+ yield
871
+ ensure
872
+ self.scoped_methods = nil
824
873
  end
825
874
 
826
875
  # Overwrite the default class equality method to provide support for association proxies.
@@ -883,21 +932,23 @@ module ActiveRecord #:nodoc:
883
932
  end
884
933
 
885
934
  def add_limit!(sql, options)
935
+ options[:limit] ||= scope(:find, :limit)
936
+ options[:offset] ||= scope(:find, :offset)
886
937
  connection.add_limit_offset!(sql, options)
887
938
  end
888
-
939
+
889
940
  def add_joins!(sql, options)
890
- join = scope_constraints[:joins] || options[:joins]
941
+ join = scope(:find, :joins) || options[:joins]
891
942
  sql << " #{join} " if join
892
943
  end
893
-
944
+
894
945
  # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
895
946
  def add_conditions!(sql, conditions)
896
- condition_segments = [scope_constraints[:conditions]]
897
- condition_segments << sanitize_sql(conditions) unless conditions.nil?
898
- condition_segments << type_condition unless descends_from_active_record?
899
- condition_segments.compact!
900
- sql << "WHERE (#{condition_segments.join(") AND (")}) " unless condition_segments.empty?
947
+ segments = [scope(:find, :conditions)]
948
+ segments << sanitize_sql(conditions) unless conditions.nil?
949
+ segments << type_condition unless descends_from_active_record?
950
+ segments.compact!
951
+ sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
901
952
  end
902
953
 
903
954
  def type_condition
@@ -922,27 +973,54 @@ module ActiveRecord #:nodoc:
922
973
  # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
923
974
  # is actually find_all_by_amount(amount, options).
924
975
  def method_missing(method_id, *arguments)
925
- method_name = method_id.id2name
976
+ if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
977
+ finder = determine_finder(match)
926
978
 
927
- if md = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
928
- finder = md.captures.first == 'all_by' ? :all : :first
929
- attributes = md.captures.last.split('_and_')
930
- attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) }
979
+ attribute_names = extract_attribute_names_from_match(match)
980
+ super unless all_attributes_exists?(attribute_names)
931
981
 
932
- attr_index = -1
933
- conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
982
+ conditions = construct_conditions_from_arguments(attribute_names, arguments)
934
983
 
935
- if arguments[attributes.length].is_a?(Hash)
936
- find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length]))
984
+ if arguments[attribute_names.length].is_a?(Hash)
985
+ find(finder, { :conditions => conditions }.update(arguments[attribute_names.length]))
937
986
  else
938
- # deprecated API
939
- send("find_#{finder}", [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
987
+ send("find_#{finder}", conditions, *arguments[attribute_names.length..-1]) # deprecated API
940
988
  end
989
+ elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
990
+ attribute_names = extract_attribute_names_from_match(match)
991
+ super unless all_attributes_exists?(attribute_names)
992
+
993
+ find(:first, :conditions => construct_conditions_from_arguments(attribute_names, arguments)) ||
994
+ create(construct_attributes_from_arguments(attribute_names, arguments))
941
995
  else
942
996
  super
943
997
  end
944
998
  end
945
999
 
1000
+ def determine_finder(match)
1001
+ match.captures.first == 'all_by' ? :all : :first
1002
+ end
1003
+
1004
+ def extract_attribute_names_from_match(match)
1005
+ match.captures.last.split('_and_')
1006
+ end
1007
+
1008
+ def construct_conditions_from_arguments(attribute_names, arguments)
1009
+ conditions = []
1010
+ attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{name} #{attribute_condition(arguments[idx])} " }
1011
+ [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
1012
+ end
1013
+
1014
+ def construct_attributes_from_arguments(attribute_names, arguments)
1015
+ attributes = {}
1016
+ attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
1017
+ attributes
1018
+ end
1019
+
1020
+ def all_attributes_exists?(attribute_names)
1021
+ attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1022
+ end
1023
+
946
1024
  def attribute_condition(argument)
947
1025
  case argument
948
1026
  when nil then "IS ?"
@@ -986,29 +1064,37 @@ module ActiveRecord #:nodoc:
986
1064
  @@subclasses[self] ||= []
987
1065
  @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
988
1066
  end
989
-
990
- def scope_constraints
1067
+
1068
+ # Test whether the given method and optional key are scoped.
1069
+ def scoped?(method, key = nil)
1070
+ scoped_methods and scoped_methods.has_key?(method) and (key.nil? or scope(method).has_key?(key))
1071
+ end
1072
+
1073
+ # Retrieve the scope for the given method and optional key.
1074
+ def scope(method, key = nil)
1075
+ if scoped_methods and scope = scoped_methods[method]
1076
+ key ? scope[key] : scope
1077
+ end
1078
+ end
1079
+
1080
+ def scoped_methods
991
1081
  if allow_concurrency
992
- Thread.current[:constraints] ||= {}
993
- Thread.current[:constraints][self] ||= {}
1082
+ Thread.current[:scoped_methods] ||= {}
1083
+ Thread.current[:scoped_methods][self] ||= nil
994
1084
  else
995
- @scope_constraints ||= {}
1085
+ @scoped_methods ||= nil
996
1086
  end
997
1087
  end
998
- # backwards compatibility
999
- alias_method :scope_constrains, :scope_constraints
1000
1088
 
1001
- def scope_constraints=(value)
1089
+ def scoped_methods=(value)
1002
1090
  if allow_concurrency
1003
- Thread.current[:constraints] ||= {}
1004
- Thread.current[:constraints][self] = value
1091
+ Thread.current[:scoped_methods] ||= {}
1092
+ Thread.current[:scoped_methods][self] = value
1005
1093
  else
1006
- @scope_constraints = value
1094
+ @scoped_methods = value
1007
1095
  end
1008
1096
  end
1009
- # backwards compatibility
1010
- alias_method :scope_constrains=, :scope_constraints=
1011
-
1097
+
1012
1098
  # Returns the class type of the record using the current module as a prefix. So descendents of
1013
1099
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
1014
1100
  def compute_type(type_name)
@@ -1108,14 +1194,13 @@ module ActiveRecord #:nodoc:
1108
1194
  yield self if block_given?
1109
1195
  end
1110
1196
 
1111
- # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
1112
- # id method, which isn't being used in this context.
1197
+ # A model instance's primary key is always available as model.id
1198
+ # whether you name it the default 'id' or set it to something else.
1113
1199
  def id
1114
1200
  attr_name = self.class.primary_key
1115
- column = column_for_attribute(attr_name)
1116
- raise ActiveRecordError, "No such primary key column #{attr_name} for table #{table_name}" if column.nil?
1201
+ column = column_for_attribute(attr_name)
1117
1202
  define_read_method(:id, attr_name, column) if self.class.generate_read_methods
1118
- (value = @attributes[attr_name]) && column.type_cast(value)
1203
+ read_attribute(attr_name)
1119
1204
  end
1120
1205
 
1121
1206
  # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
@@ -1447,17 +1532,17 @@ module ActiveRecord #:nodoc:
1447
1532
  end
1448
1533
  end
1449
1534
 
1450
- # Define a column type specific reader method.
1535
+ # Define an attribute reader method. Cope with nil column.
1451
1536
  def define_read_method(symbol, attr_name, column)
1452
- cast_code = column.type_cast_code('v')
1537
+ cast_code = column.type_cast_code('v') if column
1453
1538
  access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
1454
1539
 
1455
1540
  unless attr_name.to_s == self.class.primary_key.to_s
1456
1541
  access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
1542
+ self.class.read_methods << attr_name
1457
1543
  end
1458
1544
 
1459
1545
  self.class.class_eval("def #{symbol}; #{access_code}; end")
1460
- self.class.read_methods[attr_name] = true unless symbol == :id
1461
1546
  end
1462
1547
 
1463
1548
  # Returns true if the attribute is of a text column and marked for serialization.