activerecord 3.0.0.rc → 3.0.0.rc2
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.
- data/CHANGELOG +6 -1
- data/README.rdoc +9 -9
- data/lib/active_record/aggregations.rb +64 -51
- data/lib/active_record/association_preload.rb +11 -9
- data/lib/active_record/associations.rb +300 -204
- data/lib/active_record/associations/association_collection.rb +7 -2
- data/lib/active_record/associations/belongs_to_association.rb +9 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +7 -6
- data/lib/active_record/associations/has_many_association.rb +6 -6
- data/lib/active_record/associations/has_many_through_association.rb +4 -3
- data/lib/active_record/associations/has_one_association.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -1
- data/lib/active_record/attribute_methods/write.rb +2 -2
- data/lib/active_record/autosave_association.rb +54 -72
- data/lib/active_record/base.rb +167 -108
- data/lib/active_record/callbacks.rb +43 -35
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -11
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +0 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -3
- data/lib/active_record/connection_adapters/mysql_adapter.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -5
- data/lib/active_record/dynamic_finder_match.rb +3 -3
- data/lib/active_record/dynamic_scope_match.rb +1 -1
- data/lib/active_record/errors.rb +9 -5
- data/lib/active_record/fixtures.rb +36 -22
- data/lib/active_record/locale/en.yml +2 -2
- data/lib/active_record/migration.rb +36 -36
- data/lib/active_record/named_scope.rb +23 -11
- data/lib/active_record/nested_attributes.rb +3 -3
- data/lib/active_record/observer.rb +3 -3
- data/lib/active_record/persistence.rb +44 -29
- data/lib/active_record/railtie.rb +5 -8
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/reflection.rb +52 -52
- data/lib/active_record/relation.rb +26 -19
- data/lib/active_record/relation/batches.rb +4 -4
- data/lib/active_record/relation/calculations.rb +58 -34
- data/lib/active_record/relation/finder_methods.rb +21 -12
- data/lib/active_record/relation/query_methods.rb +26 -31
- data/lib/active_record/relation/spawn_methods.rb +17 -5
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +12 -12
- data/lib/active_record/serialization.rb +1 -1
- data/lib/active_record/serializers/xml_serializer.rb +1 -1
- data/lib/active_record/session_store.rb +9 -9
- data/lib/active_record/test_case.rb +2 -2
- data/lib/active_record/timestamp.rb +31 -32
- data/lib/active_record/validations/associated.rb +4 -3
- data/lib/active_record/validations/uniqueness.rb +15 -11
- data/lib/active_record/version.rb +1 -1
- metadata +17 -16
@@ -422,7 +422,7 @@ module ActiveRecord
|
|
422
422
|
match = DynamicFinderMatch.match(method)
|
423
423
|
if match && match.creator?
|
424
424
|
attributes = match.attribute_names
|
425
|
-
return send(:"find_by_#{attributes.join('
|
425
|
+
return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
|
426
426
|
end
|
427
427
|
|
428
428
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
@@ -492,7 +492,12 @@ module ActiveRecord
|
|
492
492
|
def create_record(attrs)
|
493
493
|
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
494
494
|
ensure_owner_is_not_new
|
495
|
-
|
495
|
+
|
496
|
+
_scope = self.construct_scope[:create]
|
497
|
+
csm = @reflection.klass.send(:current_scoped_methods)
|
498
|
+
options = (csm.blank? || !_scope.is_a?(Hash)) ? _scope : _scope.merge(csm.where_values_hash)
|
499
|
+
|
500
|
+
record = @reflection.klass.send(:with_scope, :create => options) do
|
496
501
|
@reflection.build_association(attrs)
|
497
502
|
end
|
498
503
|
if block_given?
|
@@ -22,7 +22,7 @@ module ActiveRecord
|
|
22
22
|
else
|
23
23
|
raise_on_type_mismatch(record)
|
24
24
|
|
25
|
-
if counter_cache_name && !@owner.new_record?
|
25
|
+
if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
|
26
26
|
@reflection.klass.increment_counter(counter_cache_name, record.id)
|
27
27
|
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
28
28
|
end
|
@@ -49,12 +49,16 @@ module ActiveRecord
|
|
49
49
|
else
|
50
50
|
"find"
|
51
51
|
end
|
52
|
+
|
53
|
+
options = @reflection.options.dup
|
54
|
+
(options.keys - [:select, :include, :readonly]).each do |key|
|
55
|
+
options.delete key
|
56
|
+
end
|
57
|
+
options[:conditions] = conditions
|
58
|
+
|
52
59
|
the_target = @reflection.klass.send(find_method,
|
53
60
|
@owner[@reflection.primary_key_name],
|
54
|
-
|
55
|
-
:conditions => conditions,
|
56
|
-
:include => @reflection.options[:include],
|
57
|
-
:readonly => @reflection.options[:readonly]
|
61
|
+
options
|
58
62
|
) if @owner[@reflection.primary_key_name]
|
59
63
|
set_inverse_instance(the_target, @owner)
|
60
64
|
the_target
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
24
24
|
|
25
25
|
protected
|
26
26
|
def construct_find_options!(options)
|
27
|
-
options[:joins] = @join_sql
|
27
|
+
options[:joins] = Arel::SqlLiteral.new @join_sql
|
28
28
|
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
29
29
|
options[:select] ||= (@reflection.options[:select] || '*')
|
30
30
|
end
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
79
79
|
else
|
80
80
|
relation = Arel::Table.new(@reflection.options[:join_table])
|
81
81
|
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
82
|
-
and(
|
82
|
+
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }))
|
83
83
|
).delete
|
84
84
|
end
|
85
85
|
end
|
@@ -106,9 +106,10 @@ module ActiveRecord
|
|
106
106
|
:limit => @reflection.options[:limit] } }
|
107
107
|
end
|
108
108
|
|
109
|
-
# Join tables with additional columns on top of the two foreign keys must be considered
|
110
|
-
# clause has been explicitly defined. Otherwise you can get
|
111
|
-
#
|
109
|
+
# Join tables with additional columns on top of the two foreign keys must be considered
|
110
|
+
# ambiguous unless a select clause has been explicitly defined. Otherwise you can get
|
111
|
+
# broken records back, if, for example, the join column also has an id column. This will
|
112
|
+
# then overwrite the id column of the records coming back.
|
112
113
|
def finding_with_ambiguous_select?(select_clause)
|
113
114
|
!select_clause && columns.size != 2
|
114
115
|
end
|
@@ -126,7 +127,7 @@ module ActiveRecord
|
|
126
127
|
|
127
128
|
def record_timestamp_columns(record)
|
128
129
|
if record.record_timestamps
|
129
|
-
record.send(:all_timestamp_attributes).map
|
130
|
+
record.send(:all_timestamp_attributes).map { |x| x.to_s }
|
130
131
|
else
|
131
132
|
[]
|
132
133
|
end
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
24
24
|
# If the association has a counter cache it gets that value. Otherwise
|
25
25
|
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
26
26
|
# there's one. Some configuration options like :group make it impossible
|
27
|
-
# to do
|
27
|
+
# to do an SQL count, in those cases the array count will be used.
|
28
28
|
#
|
29
29
|
# That does not depend on whether the collection has already been loaded
|
30
30
|
# or not. The +size+ method is the one that takes the loaded flag into
|
@@ -76,7 +76,7 @@ module ActiveRecord
|
|
76
76
|
else
|
77
77
|
relation = Arel::Table.new(@reflection.table_name)
|
78
78
|
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
79
|
-
and(
|
79
|
+
and(relation[@reflection.klass.primary_key].in(records.map { |r| r.id }))
|
80
80
|
).update(relation[@reflection.primary_key_name] => nil)
|
81
81
|
|
82
82
|
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
@@ -110,10 +110,10 @@ module ActiveRecord
|
|
110
110
|
create_scoping = {}
|
111
111
|
set_belongs_to_association_for(create_scoping)
|
112
112
|
{
|
113
|
-
:find => { :conditions => @finder_sql,
|
114
|
-
:readonly => false,
|
115
|
-
:order => @reflection.options[:order],
|
116
|
-
:limit => @reflection.options[:limit],
|
113
|
+
:find => { :conditions => @finder_sql,
|
114
|
+
:readonly => false,
|
115
|
+
:order => @reflection.options[:order],
|
116
|
+
:limit => @reflection.options[:limit],
|
117
117
|
:include => @reflection.options[:include]},
|
118
118
|
:create => create_scoping
|
119
119
|
}
|
@@ -24,9 +24,10 @@ module ActiveRecord
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
|
28
|
-
# calling collection.size if it has. If it's more likely than not that the collection does
|
29
|
-
# and you need to fetch that collection afterwards, it'll take one fewer
|
27
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
|
28
|
+
# loaded and calling collection.size if it has. If it's more likely than not that the collection does
|
29
|
+
# have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
|
30
|
+
# SELECT query if you use #length.
|
30
31
|
def size
|
31
32
|
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
|
32
33
|
return @target.size if loaded?
|
@@ -79,13 +79,13 @@ module ActiveRecord
|
|
79
79
|
|
80
80
|
private
|
81
81
|
def find_target
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
)
|
82
|
+
options = @reflection.options.dup
|
83
|
+
(options.keys - [:select, :order, :include, :readonly]).each do |key|
|
84
|
+
options.delete key
|
85
|
+
end
|
86
|
+
options[:conditions] = @finder_sql
|
87
|
+
|
88
|
+
the_target = @reflection.klass.find(:first, options)
|
89
89
|
set_inverse_instance(the_target, @owner)
|
90
90
|
the_target
|
91
91
|
end
|
@@ -14,7 +14,8 @@ module ActiveRecord
|
|
14
14
|
module ClassMethods
|
15
15
|
protected
|
16
16
|
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
17
|
-
# This enhanced read method automatically converts the UTC time stored in the database to the time
|
17
|
+
# This enhanced read method automatically converts the UTC time stored in the database to the time
|
18
|
+
# zone stored in Time.zone.
|
18
19
|
def define_method_attribute(attr_name)
|
19
20
|
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
20
21
|
method_body, line = <<-EOV, __LINE__ + 1
|
@@ -14,8 +14,8 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
|
18
|
-
# columns are turned into +nil+.
|
17
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
|
18
|
+
# for fixnum and float columns are turned into +nil+.
|
19
19
|
def write_attribute(attr_name, value)
|
20
20
|
attr_name = attr_name.to_s
|
21
21
|
attr_name = self.class.primary_key if attr_name == 'id'
|
@@ -2,16 +2,15 @@ require 'active_support/core_ext/array/wrap'
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
# = Active Record Autosave Association
|
5
|
-
#
|
6
|
-
# AutosaveAssociation is a module that takes care of automatically saving
|
7
|
-
#
|
8
|
-
# also destroys any
|
9
|
-
# (See mark_for_destruction and marked_for_destruction
|
5
|
+
#
|
6
|
+
# +AutosaveAssociation+ is a module that takes care of automatically saving
|
7
|
+
# associacted records when their parent is saved. In addition to saving, it
|
8
|
+
# also destroys any associated records that were marked for destruction.
|
9
|
+
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
|
10
10
|
#
|
11
11
|
# Saving of the parent, its associations, and the destruction of marked
|
12
|
-
# associations, all happen inside
|
13
|
-
# database in an inconsistent state
|
14
|
-
# attributes and saving them.
|
12
|
+
# associations, all happen inside a transaction. This should never leave the
|
13
|
+
# database in an inconsistent state.
|
15
14
|
#
|
16
15
|
# If validations for any of the associations fail, their error messages will
|
17
16
|
# be applied to the parent.
|
@@ -19,9 +18,10 @@ module ActiveRecord
|
|
19
18
|
# Note that it also means that associations marked for destruction won't
|
20
19
|
# be destroyed directly. They will however still be marked for destruction.
|
21
20
|
#
|
22
|
-
#
|
21
|
+
# Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
|
22
|
+
# When the <tt>:autosave</tt> option is not present new associations are saved.
|
23
23
|
#
|
24
|
-
#
|
24
|
+
# === One-to-one Example
|
25
25
|
#
|
26
26
|
# class Post
|
27
27
|
# has_one :author, :autosave => true
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
# automatically _and_ atomically:
|
32
32
|
#
|
33
33
|
# post = Post.find(1)
|
34
|
-
# post.title
|
34
|
+
# post.title # => "The current global position of migrating ducks"
|
35
35
|
# post.author.name # => "alloy"
|
36
36
|
#
|
37
37
|
# post.title = "On the migration of ducks"
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
39
39
|
#
|
40
40
|
# post.save
|
41
41
|
# post.reload
|
42
|
-
# post.title
|
42
|
+
# post.title # => "On the migration of ducks"
|
43
43
|
# post.author.name # => "Eloy Duran"
|
44
44
|
#
|
45
45
|
# Destroying an associated model, as part of the parent's save action, is as
|
@@ -49,6 +49,7 @@ module ActiveRecord
|
|
49
49
|
# post.author.marked_for_destruction? # => true
|
50
50
|
#
|
51
51
|
# Note that the model is _not_ yet removed from the database:
|
52
|
+
#
|
52
53
|
# id = post.author.id
|
53
54
|
# Author.find_by_id(id).nil? # => false
|
54
55
|
#
|
@@ -56,40 +57,49 @@ module ActiveRecord
|
|
56
57
|
# post.reload.author # => nil
|
57
58
|
#
|
58
59
|
# Now it _is_ removed from the database:
|
60
|
+
#
|
59
61
|
# Author.find_by_id(id).nil? # => true
|
60
62
|
#
|
61
63
|
# === One-to-many Example
|
62
64
|
#
|
63
|
-
#
|
65
|
+
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
|
64
66
|
#
|
65
67
|
# class Post
|
66
|
-
# has_many :comments
|
68
|
+
# has_many :comments # :autosave option is no declared
|
67
69
|
# end
|
68
70
|
#
|
69
|
-
#
|
70
|
-
#
|
71
|
+
# post = Post.new(:title => 'ruby rocks')
|
72
|
+
# post.comments.build(:body => 'hello world')
|
73
|
+
# post.save # => saves both post and comment
|
71
74
|
#
|
72
|
-
# post = Post.
|
73
|
-
# post.
|
74
|
-
# post.
|
75
|
-
# post.comments.last.body # => "Actually, your article should be named differently."
|
75
|
+
# post = Post.create(:title => 'ruby rocks')
|
76
|
+
# post.comments.build(:body => 'hello world')
|
77
|
+
# post.save # => saves both post and comment
|
76
78
|
#
|
77
|
-
# post
|
78
|
-
# post.comments.
|
79
|
+
# post = Post.create(:title => 'ruby rocks')
|
80
|
+
# post.comments.create(:body => 'hello world')
|
81
|
+
# post.save # => saves both post and comment
|
79
82
|
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
83
|
+
# When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
|
84
|
+
#
|
85
|
+
# class Post
|
86
|
+
# has_many :comments, :autosave => true
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# post = Post.create(:title => 'ruby rocks')
|
90
|
+
# post.comments.create(:body => 'hello world')
|
91
|
+
# post.comments[0].body = 'hi everyone'
|
92
|
+
# post.save # => saves both post and comment, with 'hi everyone' as title
|
84
93
|
#
|
85
|
-
# Destroying one of the associated models
|
86
|
-
#
|
94
|
+
# Destroying one of the associated models as part of the parent's save action
|
95
|
+
# is as simple as marking it for destruction:
|
87
96
|
#
|
88
97
|
# post.comments.last.mark_for_destruction
|
89
98
|
# post.comments.last.marked_for_destruction? # => true
|
90
99
|
# post.comments.length # => 2
|
91
100
|
#
|
92
101
|
# Note that the model is _not_ yet removed from the database:
|
102
|
+
#
|
93
103
|
# id = post.comments.last.id
|
94
104
|
# Comment.find_by_id(id).nil? # => false
|
95
105
|
#
|
@@ -97,37 +107,12 @@ module ActiveRecord
|
|
97
107
|
# post.reload.comments.length # => 1
|
98
108
|
#
|
99
109
|
# Now it _is_ removed from the database:
|
110
|
+
#
|
100
111
|
# Comment.find_by_id(id).nil? # => true
|
101
112
|
#
|
102
113
|
# === Validation
|
103
114
|
#
|
104
|
-
#
|
105
|
-
# enabled associations. If any of the associations fail validation, its
|
106
|
-
# error messages will be applied on the parents errors object and validation
|
107
|
-
# of the parent will fail.
|
108
|
-
#
|
109
|
-
# Consider a Post model with Author which validates the presence of its name
|
110
|
-
# attribute:
|
111
|
-
#
|
112
|
-
# class Post
|
113
|
-
# has_one :author, :autosave => true
|
114
|
-
# end
|
115
|
-
#
|
116
|
-
# class Author
|
117
|
-
# validates_presence_of :name
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# post = Post.find(1)
|
121
|
-
# post.author.name = ''
|
122
|
-
# post.save # => false
|
123
|
-
# post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author.name"=>["can't be blank"]}, @base=#<Post ...>>
|
124
|
-
#
|
125
|
-
# No validations will be performed on the associated models when validations
|
126
|
-
# are skipped for the parent:
|
127
|
-
#
|
128
|
-
# post = Post.find(1)
|
129
|
-
# post.author.name = ''
|
130
|
-
# post.save(:validate => false) # => true
|
115
|
+
# Children records are validated unless <tt>:validate</tt> is +false+.
|
131
116
|
module AutosaveAssociation
|
132
117
|
extend ActiveSupport::Concern
|
133
118
|
|
@@ -155,11 +140,12 @@ module ActiveRecord
|
|
155
140
|
CODE
|
156
141
|
end
|
157
142
|
|
158
|
-
# Adds
|
143
|
+
# Adds validation and save callbacks for the association as specified by
|
159
144
|
# the +reflection+.
|
160
145
|
#
|
161
|
-
# For performance reasons, we don't check whether to validate at runtime
|
162
|
-
#
|
146
|
+
# For performance reasons, we don't check whether to validate at runtime.
|
147
|
+
# However the validation and callback methods are lazy and those methods
|
148
|
+
# get created when they are invoked for the very first time. However,
|
163
149
|
# this can change, for instance, when using nested attributes, which is
|
164
150
|
# called _after_ the association has been defined. Since we don't want
|
165
151
|
# the callbacks to get defined multiple times, there are guards that
|
@@ -197,14 +183,15 @@ module ActiveRecord
|
|
197
183
|
end
|
198
184
|
end
|
199
185
|
|
200
|
-
# Reloads the attributes of the object as usual and
|
186
|
+
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
|
201
187
|
def reload(options = nil)
|
202
188
|
@marked_for_destruction = false
|
203
189
|
super
|
204
190
|
end
|
205
191
|
|
206
192
|
# Marks this record to be destroyed as part of the parents save transaction.
|
207
|
-
# This does _not_ actually destroy the record
|
193
|
+
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
|
194
|
+
# when <tt>parent.save</tt> is called.
|
208
195
|
#
|
209
196
|
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
210
197
|
def mark_for_destruction
|
@@ -223,7 +210,7 @@ module ActiveRecord
|
|
223
210
|
def changed_for_autosave?
|
224
211
|
new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
|
225
212
|
end
|
226
|
-
|
213
|
+
|
227
214
|
private
|
228
215
|
|
229
216
|
# Returns the record for an association collection that should be validated
|
@@ -244,12 +231,12 @@ module ActiveRecord
|
|
244
231
|
def nested_records_changed_for_autosave?
|
245
232
|
self.class.reflect_on_all_autosave_associations.any? do |reflection|
|
246
233
|
association = association_instance_get(reflection.name)
|
247
|
-
association && Array.wrap(association.target).any?
|
234
|
+
association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
|
248
235
|
end
|
249
236
|
end
|
250
|
-
|
237
|
+
|
251
238
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
252
|
-
# turned on for the association
|
239
|
+
# turned on for the association.
|
253
240
|
def validate_single_association(reflection)
|
254
241
|
if (association = association_instance_get(reflection.name)) && !association.target.nil?
|
255
242
|
association_valid?(reflection, association)
|
@@ -357,14 +344,9 @@ module ActiveRecord
|
|
357
344
|
end
|
358
345
|
end
|
359
346
|
|
360
|
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
|
361
|
-
# on the association.
|
347
|
+
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
362
348
|
#
|
363
|
-
# In addition, it will destroy the association if it was marked for
|
364
|
-
# destruction with mark_for_destruction.
|
365
|
-
#
|
366
|
-
# This all happens inside a transaction, _if_ the Transactions module is included into
|
367
|
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
349
|
+
# In addition, it will destroy the association if it was marked for destruction.
|
368
350
|
def save_belongs_to_association(reflection)
|
369
351
|
if (association = association_instance_get(reflection.name)) && !association.destroyed?
|
370
352
|
autosave = reflection.options[:autosave]
|
@@ -384,4 +366,4 @@ module ActiveRecord
|
|
384
366
|
end
|
385
367
|
end
|
386
368
|
end
|
387
|
-
end
|
369
|
+
end
|
data/lib/active_record/base.rb
CHANGED
@@ -26,17 +26,19 @@ require 'active_record/log_subscriber'
|
|
26
26
|
module ActiveRecord #:nodoc:
|
27
27
|
# = Active Record
|
28
28
|
#
|
29
|
-
# Active Record objects don't specify their attributes directly, but rather infer them from
|
30
|
-
# which they're linked. Adding, removing, and changing attributes
|
31
|
-
# is
|
29
|
+
# Active Record objects don't specify their attributes directly, but rather infer them from
|
30
|
+
# the table definition with which they're linked. Adding, removing, and changing attributes
|
31
|
+
# and their type is done directly in the database. Any change is instantly reflected in the
|
32
|
+
# Active Record objects. The mapping that binds a given Active Record class to a certain
|
32
33
|
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
|
33
34
|
#
|
34
35
|
# See the mapping rules in table_name and the full example in link:files/README.html for more insight.
|
35
36
|
#
|
36
37
|
# == Creation
|
37
38
|
#
|
38
|
-
# Active Records accept constructor parameters either in a hash or as a block. The hash
|
39
|
-
# you're receiving the data from somewhere else, like an
|
39
|
+
# Active Records accept constructor parameters either in a hash or as a block. The hash
|
40
|
+
# method is especially useful when you're receiving the data from somewhere else, like an
|
41
|
+
# HTTP request. It works like this:
|
40
42
|
#
|
41
43
|
# user = User.new(:name => "David", :occupation => "Code Artist")
|
42
44
|
# user.name # => "David"
|
@@ -75,14 +77,17 @@ module ActiveRecord #:nodoc:
|
|
75
77
|
# end
|
76
78
|
# end
|
77
79
|
#
|
78
|
-
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
|
79
|
-
# attacks if the <tt>user_name</tt> and +password+
|
80
|
-
#
|
81
|
-
#
|
80
|
+
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
|
81
|
+
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
|
82
|
+
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
|
83
|
+
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
|
84
|
+
# before inserting them in the query, which will ensure that an attacker can't escape the
|
85
|
+
# query and fake the login (or worse).
|
82
86
|
#
|
83
|
-
# When using multiple parameters in the conditions, it can easily become hard to read exactly
|
84
|
-
# question mark is supposed to represent. In those cases, you can
|
85
|
-
#
|
87
|
+
# When using multiple parameters in the conditions, it can easily become hard to read exactly
|
88
|
+
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
|
89
|
+
# resort to named bind variables instead. That's done by replacing the question marks with
|
90
|
+
# symbols and supplying a hash with values for the matching symbol keys:
|
86
91
|
#
|
87
92
|
# Company.where(
|
88
93
|
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
@@ -103,18 +108,19 @@ module ActiveRecord #:nodoc:
|
|
103
108
|
#
|
104
109
|
# Student.where(:grade => [9,11,12])
|
105
110
|
#
|
106
|
-
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
|
107
|
-
# particular condition. For instance:
|
111
|
+
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
|
112
|
+
# can be used to qualify the table name of a particular condition. For instance:
|
108
113
|
#
|
109
114
|
# Student.joins(:schools).where(:schools => { :type => 'public' })
|
110
115
|
# Student.joins(:schools).where('schools.type' => 'public' )
|
111
116
|
#
|
112
117
|
# == Overwriting default accessors
|
113
118
|
#
|
114
|
-
# All column values are automatically available through basic accessors on the Active Record
|
115
|
-
# want to specialize this behavior. This can be done by overwriting
|
116
|
-
# name as the attribute) and calling
|
117
|
-
#
|
119
|
+
# All column values are automatically available through basic accessors on the Active Record
|
120
|
+
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
|
121
|
+
# the default accessors (using the same name as the attribute) and calling
|
122
|
+
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
|
123
|
+
# change things.
|
118
124
|
#
|
119
125
|
# class Song < ActiveRecord::Base
|
120
126
|
# # Uses an integer of seconds to hold the length of the song
|
@@ -128,8 +134,8 @@ module ActiveRecord #:nodoc:
|
|
128
134
|
# end
|
129
135
|
# end
|
130
136
|
#
|
131
|
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
|
132
|
-
# <tt>
|
137
|
+
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
|
138
|
+
# instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
|
133
139
|
#
|
134
140
|
# == Attribute query methods
|
135
141
|
#
|
@@ -147,34 +153,43 @@ module ActiveRecord #:nodoc:
|
|
147
153
|
#
|
148
154
|
# == Accessing attributes before they have been typecasted
|
149
155
|
#
|
150
|
-
# Sometimes you want to be able to read the raw attribute data without having the column-determined
|
151
|
-
# That can be done by using the <tt><attribute>_before_type_cast</tt>
|
152
|
-
#
|
156
|
+
# Sometimes you want to be able to read the raw attribute data without having the column-determined
|
157
|
+
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
|
158
|
+
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
|
159
|
+
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
|
153
160
|
#
|
154
|
-
# This is especially useful in validation situations where the user might supply a string for an
|
155
|
-
# the original string back in an error message. Accessing the
|
156
|
-
# want.
|
161
|
+
# This is especially useful in validation situations where the user might supply a string for an
|
162
|
+
# integer field and you want to display the original string back in an error message. Accessing the
|
163
|
+
# attribute normally would typecast the string to 0, which isn't what you want.
|
157
164
|
#
|
158
165
|
# == Dynamic attribute-based finders
|
159
166
|
#
|
160
|
-
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
|
161
|
-
#
|
162
|
-
# <tt>
|
167
|
+
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
|
168
|
+
# by simple queries without turning to SQL. They work by appending the name of an attribute
|
169
|
+
# to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
|
170
|
+
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
|
171
|
+
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
|
163
172
|
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
164
|
-
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
173
|
+
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
174
|
+
# <tt>Person.find_all_by_last_name(last_name)</tt>.
|
165
175
|
#
|
166
|
-
# It's also possible to use multiple attributes in the same find by separating them with "_and_"
|
167
|
-
# <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
|
168
|
-
# <tt>Person.where(:user_name => user_name, :password => password).first</tt>, you just do
|
169
|
-
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
|
176
|
+
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
|
170
177
|
#
|
171
|
-
#
|
178
|
+
# Person.where(:user_name => user_name, :password => password).first
|
179
|
+
# Person.find_by_user_name_and_password #with dynamic finder
|
180
|
+
#
|
181
|
+
# Person.where(:user_name => user_name, :password => password, :gender => 'male').first
|
182
|
+
# Payment.find_by_user_name_and_password_and_gender
|
183
|
+
#
|
184
|
+
# It's even possible to call these dynamic finder methods on relations and named scopes.
|
172
185
|
#
|
173
186
|
# Payment.order("created_on").find_all_by_amount(50)
|
174
187
|
# Payment.pending.find_last_by_amount(100)
|
175
188
|
#
|
176
|
-
# The same dynamic finder style can be used to create the object if it doesn't already exist.
|
177
|
-
# <tt>find_or_create_by_</tt> and will return the object if
|
189
|
+
# The same dynamic finder style can be used to create the object if it doesn't already exist.
|
190
|
+
# This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
|
191
|
+
# it already exists and otherwise creates it, then returns it. Protected attributes won't be set
|
192
|
+
# unless they are given in a block.
|
178
193
|
#
|
179
194
|
# # No 'Summer' tag exists
|
180
195
|
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
|
@@ -185,23 +200,33 @@ module ActiveRecord #:nodoc:
|
|
185
200
|
# # Now 'Bob' exist and is an 'admin'
|
186
201
|
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
187
202
|
#
|
188
|
-
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
|
203
|
+
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
|
204
|
+
# saving it first. Protected attributes won't be set unless they are given in a block.
|
189
205
|
#
|
190
206
|
# # No 'Winter' tag exists
|
191
207
|
# winter = Tag.find_or_initialize_by_name("Winter")
|
192
208
|
# winter.new_record? # true
|
193
209
|
#
|
194
210
|
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
|
195
|
-
# a list of parameters.
|
211
|
+
# a list of parameters.
|
196
212
|
#
|
197
213
|
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
|
198
214
|
#
|
199
|
-
# That will either find an existing tag named "rails", or create a new one while setting the
|
215
|
+
# That will either find an existing tag named "rails", or create a new one while setting the
|
216
|
+
# user that created it.
|
217
|
+
#
|
218
|
+
# Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
|
219
|
+
# using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
|
220
|
+
# but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
|
221
|
+
#
|
222
|
+
# User.scoped_by_user_name('David')
|
200
223
|
#
|
201
224
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
202
225
|
#
|
203
|
-
# Active Record can serialize any object in text columns using YAML. To do so, you must
|
204
|
-
#
|
226
|
+
# Active Record can serialize any object in text columns using YAML. To do so, you must
|
227
|
+
# specify this with a call to the class method +serialize+.
|
228
|
+
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
|
229
|
+
# any additional work.
|
205
230
|
#
|
206
231
|
# class User < ActiveRecord::Base
|
207
232
|
# serialize :preferences
|
@@ -210,8 +235,8 @@ module ActiveRecord #:nodoc:
|
|
210
235
|
# user = User.create(:preferences => { "background" => "black", "display" => large })
|
211
236
|
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
212
237
|
#
|
213
|
-
# You can also specify a class option as the second parameter that'll raise an exception
|
214
|
-
# descendant of a class not in the hierarchy.
|
238
|
+
# You can also specify a class option as the second parameter that'll raise an exception
|
239
|
+
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
|
215
240
|
#
|
216
241
|
# class User < ActiveRecord::Base
|
217
242
|
# serialize :preferences, Hash
|
@@ -222,52 +247,63 @@ module ActiveRecord #:nodoc:
|
|
222
247
|
#
|
223
248
|
# == Single table inheritance
|
224
249
|
#
|
225
|
-
# Active Record allows inheritance by storing the name of the class in a column that by
|
226
|
-
# by overwriting <tt>Base.inheritance_column</tt>).
|
250
|
+
# Active Record allows inheritance by storing the name of the class in a column that by
|
251
|
+
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
|
252
|
+
# This means that an inheritance looking like this:
|
227
253
|
#
|
228
254
|
# class Company < ActiveRecord::Base; end
|
229
255
|
# class Firm < Company; end
|
230
256
|
# class Client < Company; end
|
231
257
|
# class PriorityClient < Client; end
|
232
258
|
#
|
233
|
-
# When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
|
234
|
-
#
|
259
|
+
# When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
|
260
|
+
# the companies table with type = "Firm". You can then fetch this row again using
|
261
|
+
# <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
|
235
262
|
#
|
236
|
-
# If you don't have a type column defined in your table, single-table inheritance won't
|
237
|
-
#
|
263
|
+
# If you don't have a type column defined in your table, single-table inheritance won't
|
264
|
+
# be triggered. In that case, it'll work just like normal subclasses with no special magic
|
265
|
+
# for differentiating between them or reloading the right type with find.
|
238
266
|
#
|
239
267
|
# Note, all the attributes for all the cases are kept in the same table. Read more:
|
240
268
|
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
241
269
|
#
|
242
270
|
# == Connection to multiple databases in different models
|
243
271
|
#
|
244
|
-
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
|
245
|
-
# All classes inheriting from ActiveRecord::Base will use this
|
246
|
-
#
|
272
|
+
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
|
273
|
+
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
|
274
|
+
# connection. But you can also set a class-specific connection. For example, if Course is an
|
275
|
+
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
247
276
|
# and Course and all of its subclasses will use this connection instead.
|
248
277
|
#
|
249
|
-
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
|
250
|
-
#
|
278
|
+
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
|
279
|
+
# a Hash indexed by the class. If a connection is requested, the retrieve_connection method
|
280
|
+
# will go up the class-hierarchy until a connection is found in the connection pool.
|
251
281
|
#
|
252
282
|
# == Exceptions
|
253
283
|
#
|
254
284
|
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
|
255
285
|
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
|
256
286
|
# <tt>:adapter</tt> key.
|
257
|
-
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
|
287
|
+
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
|
288
|
+
# non-existent adapter
|
258
289
|
# (or a bad spelling of an existing one).
|
259
|
-
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
290
|
+
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
291
|
+
# specified in the association definition.
|
260
292
|
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
|
261
|
-
# * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
|
293
|
+
# * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
|
294
|
+
# before querying.
|
262
295
|
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
|
263
296
|
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
|
264
297
|
# nothing was found, please check its documentation for further details.
|
265
298
|
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
|
266
299
|
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
267
|
-
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
|
300
|
+
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
|
301
|
+
# AttributeAssignmentError
|
268
302
|
# objects that should be inspected to determine which attributes triggered the errors.
|
269
|
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
270
|
-
#
|
303
|
+
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
304
|
+
# <tt>attributes=</tt> method.
|
305
|
+
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
306
|
+
# triggered the error.
|
271
307
|
#
|
272
308
|
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
273
309
|
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
@@ -275,8 +311,9 @@ module ActiveRecord #:nodoc:
|
|
275
311
|
class Base
|
276
312
|
##
|
277
313
|
# :singleton-method:
|
278
|
-
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
|
279
|
-
# on to any new database connections made and which can be retrieved on both
|
314
|
+
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
|
315
|
+
# which is then passed on to any new database connections made and which can be retrieved on both
|
316
|
+
# a class and instance level by calling +logger+.
|
280
317
|
cattr_accessor :logger, :instance_writer => false
|
281
318
|
|
282
319
|
class << self
|
@@ -323,21 +360,24 @@ module ActiveRecord #:nodoc:
|
|
323
360
|
|
324
361
|
##
|
325
362
|
# :singleton-method:
|
326
|
-
# Accessor for the prefix type that will be prepended to every primary key column name.
|
327
|
-
# :table_name_with_underscore. If the first is specified,
|
328
|
-
# the
|
363
|
+
# Accessor for the prefix type that will be prepended to every primary key column name.
|
364
|
+
# The options are :table_name and :table_name_with_underscore. If the first is specified,
|
365
|
+
# the Product class will look for "productid" instead of "id" as the primary column. If the
|
366
|
+
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
329
367
|
# that this is a global setting for all Active Records.
|
330
368
|
cattr_accessor :primary_key_prefix_type, :instance_writer => false
|
331
369
|
@@primary_key_prefix_type = nil
|
332
370
|
|
333
371
|
##
|
334
372
|
# :singleton-method:
|
335
|
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
|
336
|
-
# table names will be named like "basecamp_projects", "basecamp_people",
|
337
|
-
#
|
373
|
+
# Accessor for the name of the prefix string to prepend to every table name. So if set
|
374
|
+
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
|
375
|
+
# etc. This is a convenient way of creating a namespace for tables in a shared database.
|
376
|
+
# By default, the prefix is the empty string.
|
338
377
|
#
|
339
|
-
# If you are organising your models within modules you can add a prefix to the models within
|
340
|
-
# a singleton method in the parent module called table_name_prefix which
|
378
|
+
# If you are organising your models within modules you can add a prefix to the models within
|
379
|
+
# a namespace by defining a singleton method in the parent module called table_name_prefix which
|
380
|
+
# returns your chosen prefix.
|
341
381
|
class_attribute :table_name_prefix, :instance_writer => false
|
342
382
|
self.table_name_prefix = ""
|
343
383
|
|
@@ -358,8 +398,8 @@ module ActiveRecord #:nodoc:
|
|
358
398
|
|
359
399
|
##
|
360
400
|
# :singleton-method:
|
361
|
-
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
|
362
|
-
# This is set to :local by default.
|
401
|
+
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
|
402
|
+
# dates and times from the database. This is set to :local by default.
|
363
403
|
cattr_accessor :default_timezone, :instance_writer => false
|
364
404
|
@@default_timezone = :local
|
365
405
|
|
@@ -476,9 +516,10 @@ module ActiveRecord #:nodoc:
|
|
476
516
|
connection.select_value(sql, "#{name} Count").to_i
|
477
517
|
end
|
478
518
|
|
479
|
-
# Attributes listed as readonly
|
519
|
+
# Attributes listed as readonly will be used to create a new record but update operations will
|
520
|
+
# ignore these fields.
|
480
521
|
def attr_readonly(*attributes)
|
481
|
-
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map
|
522
|
+
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map { |a| a.to_s }) + (readonly_attributes || []))
|
482
523
|
end
|
483
524
|
|
484
525
|
# Returns an array of all the attributes that have been specified as readonly.
|
@@ -505,15 +546,18 @@ module ActiveRecord #:nodoc:
|
|
505
546
|
serialized_attributes[attr_name.to_s] = class_name
|
506
547
|
end
|
507
548
|
|
508
|
-
# Returns a hash of all the attributes that have been specified for serialization as
|
549
|
+
# Returns a hash of all the attributes that have been specified for serialization as
|
550
|
+
# keys and their class restriction as values.
|
509
551
|
def serialized_attributes
|
510
552
|
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
|
511
553
|
end
|
512
554
|
|
513
|
-
# Guesses the table name (in forced lower-case) based on the name of the class in the
|
514
|
-
# directly from ActiveRecord::Base. So if the hierarchy
|
515
|
-
#
|
516
|
-
#
|
555
|
+
# Guesses the table name (in forced lower-case) based on the name of the class in the
|
556
|
+
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
|
557
|
+
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
|
558
|
+
# to guess the table name even when called on Reply. The rules used to do the guess
|
559
|
+
# are handled by the Inflector class in Active Support, which knows almost all common
|
560
|
+
# English inflections. You can add new inflections in config/initializers/inflections.rb.
|
517
561
|
#
|
518
562
|
# Nested classes are given table names prefixed by the singular form of
|
519
563
|
# the parent's table name. Enclosing modules are not considered.
|
@@ -561,8 +605,8 @@ module ActiveRecord #:nodoc:
|
|
561
605
|
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
|
562
606
|
end
|
563
607
|
|
564
|
-
# Defines the column name for use with single table inheritance
|
565
|
-
#
|
608
|
+
# Defines the column name for use with single table inheritance. Use
|
609
|
+
# <tt>set_inheritance_column</tt> to set a different value.
|
566
610
|
def inheritance_column
|
567
611
|
@inheritance_column ||= "type".freeze
|
568
612
|
end
|
@@ -579,8 +623,8 @@ module ActiveRecord #:nodoc:
|
|
579
623
|
default
|
580
624
|
end
|
581
625
|
|
582
|
-
# Sets the table name
|
583
|
-
# is
|
626
|
+
# Sets the table name. If the value is nil or false then the value returned by the given
|
627
|
+
# block is used.
|
584
628
|
#
|
585
629
|
# class Project < ActiveRecord::Base
|
586
630
|
# set_table_name "project"
|
@@ -803,7 +847,7 @@ module ActiveRecord #:nodoc:
|
|
803
847
|
end
|
804
848
|
|
805
849
|
def arel_table
|
806
|
-
@arel_table ||= Arel::Table.new(table_name,
|
850
|
+
@arel_table ||= Arel::Table.new(table_name, arel_engine)
|
807
851
|
end
|
808
852
|
|
809
853
|
def arel_engine
|
@@ -874,7 +918,11 @@ module ActiveRecord #:nodoc:
|
|
874
918
|
self
|
875
919
|
else
|
876
920
|
begin
|
877
|
-
|
921
|
+
if store_full_sti_class
|
922
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
923
|
+
else
|
924
|
+
compute_type(type_name)
|
925
|
+
end
|
878
926
|
rescue NameError
|
879
927
|
raise SubclassNotFound,
|
880
928
|
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
|
@@ -923,15 +971,15 @@ module ActiveRecord #:nodoc:
|
|
923
971
|
end
|
924
972
|
end
|
925
973
|
|
926
|
-
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and
|
927
|
-
#
|
928
|
-
#
|
974
|
+
# Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
|
975
|
+
# <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
|
976
|
+
# section at the top of this file for more detailed information.
|
929
977
|
#
|
930
|
-
# It's even possible to use all the additional parameters to +find+. For example, the
|
931
|
-
# is actually <tt>find_all_by_amount(amount, options)</tt>.
|
978
|
+
# It's even possible to use all the additional parameters to +find+. For example, the
|
979
|
+
# full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
|
932
980
|
#
|
933
|
-
# Each dynamic finder
|
934
|
-
# attempts to use it do not run through method_missing.
|
981
|
+
# Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
|
982
|
+
# is first invoked, so that future attempts to use it do not run through method_missing.
|
935
983
|
def method_missing(method_id, *arguments, &block)
|
936
984
|
if match = DynamicFinderMatch.match(method_id)
|
937
985
|
attribute_names = match.attribute_names
|
@@ -991,8 +1039,8 @@ module ActiveRecord #:nodoc:
|
|
991
1039
|
end
|
992
1040
|
|
993
1041
|
protected
|
994
|
-
#
|
995
|
-
#
|
1042
|
+
# with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
|
1043
|
+
# <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
996
1044
|
# <tt>:create</tt> parameters are an attributes hash.
|
997
1045
|
#
|
998
1046
|
# class Article < ActiveRecord::Base
|
@@ -1037,8 +1085,7 @@ module ActiveRecord #:nodoc:
|
|
1037
1085
|
# end
|
1038
1086
|
# end
|
1039
1087
|
#
|
1040
|
-
# *Note*: the +:find+ scope also has effect on update and deletion methods,
|
1041
|
-
# like +update_all+ and +delete_all+.
|
1088
|
+
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
|
1042
1089
|
def with_scope(method_scoping = {}, action = :merge, &block)
|
1043
1090
|
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
|
1044
1091
|
|
@@ -1104,6 +1151,16 @@ MSG
|
|
1104
1151
|
# class Person < ActiveRecord::Base
|
1105
1152
|
# default_scope order('last_name, first_name')
|
1106
1153
|
# end
|
1154
|
+
#
|
1155
|
+
# <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
1156
|
+
# applied while updating a record.
|
1157
|
+
#
|
1158
|
+
# class Article < ActiveRecord::Base
|
1159
|
+
# default_scope where(:published => true)
|
1160
|
+
# end
|
1161
|
+
#
|
1162
|
+
# Article.new.published # => true
|
1163
|
+
# Article.create.published # => true
|
1107
1164
|
def default_scope(options = {})
|
1108
1165
|
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
|
1109
1166
|
end
|
@@ -1118,7 +1175,7 @@ MSG
|
|
1118
1175
|
if type_name.match(/^::/)
|
1119
1176
|
# If the type is prefixed with a scope operator then we assume that
|
1120
1177
|
# the type_name is an absolute reference.
|
1121
|
-
|
1178
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
1122
1179
|
else
|
1123
1180
|
# Build a list of candidates to search for
|
1124
1181
|
candidates = []
|
@@ -1127,7 +1184,7 @@ MSG
|
|
1127
1184
|
|
1128
1185
|
candidates.each do |candidate|
|
1129
1186
|
begin
|
1130
|
-
constant =
|
1187
|
+
constant = ActiveSupport::Dependencies.constantize(candidate)
|
1131
1188
|
return constant if candidate == constant.to_s
|
1132
1189
|
rescue NameError => e
|
1133
1190
|
# We don't want to swallow NoMethodError < NameError errors
|
@@ -1233,7 +1290,7 @@ MSG
|
|
1233
1290
|
|
1234
1291
|
table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
|
1235
1292
|
builder = PredicateBuilder.new(arel_engine)
|
1236
|
-
builder.build_from_hash(attrs, table).map
|
1293
|
+
builder.build_from_hash(attrs, table).map{ |b| b.to_sql }.join(' AND ')
|
1237
1294
|
end
|
1238
1295
|
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
1239
1296
|
|
@@ -1357,7 +1414,7 @@ MSG
|
|
1357
1414
|
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
|
1358
1415
|
# application specific and is therefore left to the application to implement according to its need.
|
1359
1416
|
def initialize_copy(other)
|
1360
|
-
|
1417
|
+
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
1361
1418
|
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
1362
1419
|
cloned_attributes.delete(self.class.primary_key)
|
1363
1420
|
|
@@ -1607,10 +1664,11 @@ MSG
|
|
1607
1664
|
|
1608
1665
|
private
|
1609
1666
|
|
1610
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the
|
1611
|
-
#
|
1612
|
-
#
|
1613
|
-
#
|
1667
|
+
# Sets the attribute used for single table inheritance to this class name if this is not the
|
1668
|
+
# ActiveRecord::Base descendant.
|
1669
|
+
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
|
1670
|
+
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
|
1671
|
+
# No such attribute would be set for objects of the Message class in that example.
|
1614
1672
|
def ensure_proper_type
|
1615
1673
|
unless self.class.descends_from_active_record?
|
1616
1674
|
write_attribute(self.class.inheritance_column, self.class.sti_name)
|
@@ -1659,8 +1717,9 @@ MSG
|
|
1659
1717
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
1660
1718
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
1661
1719
|
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
1662
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
|
1663
|
-
# s for String, and a for Array. If all the values for a given attribute are empty, the
|
1720
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
|
1721
|
+
# f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
|
1722
|
+
# attribute will be set to nil.
|
1664
1723
|
def assign_multiparameter_attributes(pairs)
|
1665
1724
|
execute_callstack_for_multiparameter_attributes(
|
1666
1725
|
extract_callstack_for_multiparameter_attributes(pairs)
|
@@ -1682,7 +1741,7 @@ MSG
|
|
1682
1741
|
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
1683
1742
|
# in order to allow a date to be set without a year, we must keep the empty values.
|
1684
1743
|
# Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
|
1685
|
-
values = values_with_empty_parameters.reject
|
1744
|
+
values = values_with_empty_parameters.reject { |v| v.nil? }
|
1686
1745
|
|
1687
1746
|
if values.empty?
|
1688
1747
|
send(name + "=", nil)
|