activerecord 1.15.6 → 2.0.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.
- data/CHANGELOG +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def find(*args)
|
12
|
-
options =
|
12
|
+
options = args.extract_options!
|
13
13
|
|
14
14
|
conditions = "#{@finder_sql}"
|
15
15
|
if sanitized_conditions = sanitize_sql(options[:conditions])
|
@@ -51,16 +51,14 @@ module ActiveRecord
|
|
51
51
|
through = @reflection.through_reflection
|
52
52
|
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?
|
53
53
|
|
54
|
-
load_target
|
55
|
-
|
56
54
|
klass = through.klass
|
57
55
|
klass.transaction do
|
58
56
|
flatten_deeper(records).each do |associate|
|
59
57
|
raise_on_type_mismatch(associate)
|
60
58
|
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
61
59
|
|
62
|
-
@owner.send(@reflection.through_reflection.name).proxy_target << klass.
|
63
|
-
@target << associate
|
60
|
+
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
|
61
|
+
@target << associate if loaded?
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
@@ -69,9 +67,9 @@ module ActiveRecord
|
|
69
67
|
|
70
68
|
[:push, :concat].each { |method| alias_method method, :<< }
|
71
69
|
|
72
|
-
#
|
70
|
+
# Removes +records+ from this association. Does not destroy +records+.
|
73
71
|
def delete(*records)
|
74
|
-
|
72
|
+
records = flatten_deeper(records)
|
75
73
|
records.each { |associate| raise_on_type_mismatch(associate) }
|
76
74
|
|
77
75
|
through = @reflection.through_reflection
|
@@ -85,7 +83,7 @@ module ActiveRecord
|
|
85
83
|
raise_on_type_mismatch(associate)
|
86
84
|
raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
87
85
|
|
88
|
-
@owner.send(
|
86
|
+
@owner.send(through.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
|
89
87
|
@target.delete(associate)
|
90
88
|
end
|
91
89
|
end
|
@@ -96,22 +94,33 @@ module ActiveRecord
|
|
96
94
|
def build(attrs = nil)
|
97
95
|
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
|
98
96
|
end
|
97
|
+
alias_method :new, :build
|
99
98
|
|
100
99
|
def create!(attrs = nil)
|
101
100
|
@reflection.klass.transaction do
|
102
|
-
self << @reflection.klass.
|
101
|
+
self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! })
|
102
|
+
object
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
|
107
|
+
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
|
108
|
+
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
|
109
|
+
def size
|
110
|
+
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
|
111
|
+
return @target.size if loaded?
|
112
|
+
return count
|
113
|
+
end
|
114
|
+
|
106
115
|
# Calculate sum using SQL, not Enumerable
|
107
116
|
def sum(*args, &block)
|
108
117
|
calculate(:sum, *args, &block)
|
109
118
|
end
|
110
119
|
|
111
120
|
def count(*args)
|
112
|
-
column_name, options = @reflection.klass.send(:
|
121
|
+
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
113
122
|
if @reflection.options[:uniq]
|
114
|
-
# This is needed
|
123
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid sql statement.
|
115
124
|
column_name = "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key}" if column_name == :all
|
116
125
|
options.merge!(:distinct => true)
|
117
126
|
end
|
@@ -123,7 +132,7 @@ module ActiveRecord
|
|
123
132
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
124
133
|
super
|
125
134
|
else
|
126
|
-
@reflection.klass.with_scope
|
135
|
+
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
|
127
136
|
end
|
128
137
|
end
|
129
138
|
|
@@ -154,11 +163,11 @@ module ActiveRecord
|
|
154
163
|
|
155
164
|
# Construct attributes for :through pointing to owner and associate.
|
156
165
|
def construct_join_attributes(associate)
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
166
|
+
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
167
|
+
if @reflection.options[:source_type]
|
168
|
+
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
161
169
|
end
|
170
|
+
join_attributes
|
162
171
|
end
|
163
172
|
|
164
173
|
# Associate attributes pointing to owner, quoted.
|
@@ -226,7 +235,9 @@ module ActiveRecord
|
|
226
235
|
:find => { :from => construct_from,
|
227
236
|
:conditions => construct_conditions,
|
228
237
|
:joins => construct_joins,
|
229
|
-
:select => construct_select
|
238
|
+
:select => construct_select,
|
239
|
+
:order => @reflection.options[:order],
|
240
|
+
:limit => @reflection.options[:limit] } }
|
230
241
|
end
|
231
242
|
|
232
243
|
def construct_sql
|
@@ -253,11 +264,20 @@ module ActiveRecord
|
|
253
264
|
@conditions ||= [
|
254
265
|
(interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
|
255
266
|
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
|
267
|
+
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.source_reflection.options[:conditions])) if @reflection.source_reflection.options[:conditions]),
|
256
268
|
("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?)
|
257
|
-
].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
|
269
|
+
].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && !@reflection.source_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
|
258
270
|
end
|
259
271
|
|
260
272
|
alias_method :sql_conditions, :conditions
|
273
|
+
|
274
|
+
def has_cached_counter?
|
275
|
+
@owner.attribute_present?(cached_counter_attribute_name)
|
276
|
+
end
|
277
|
+
|
278
|
+
def cached_counter_attribute_name
|
279
|
+
"#{@reflection.name}_count"
|
280
|
+
end
|
261
281
|
end
|
262
282
|
end
|
263
283
|
end
|
@@ -6,23 +6,16 @@ module ActiveRecord
|
|
6
6
|
construct_sql
|
7
7
|
end
|
8
8
|
|
9
|
-
def create(
|
10
|
-
|
11
|
-
record.save
|
12
|
-
record
|
9
|
+
def create(attrs = {}, replace_existing = true)
|
10
|
+
new_record(replace_existing) { |klass| klass.create(attrs) }
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
if replace_existing
|
19
|
-
replace(record, true)
|
20
|
-
else
|
21
|
-
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
22
|
-
self.target = record
|
23
|
-
end
|
13
|
+
def create!(attrs = {}, replace_existing = true)
|
14
|
+
new_record(replace_existing) { |klass| klass.create!(attrs) }
|
15
|
+
end
|
24
16
|
|
25
|
-
|
17
|
+
def build(attrs = {}, replace_existing = true)
|
18
|
+
new_record(replace_existing) { |klass| klass.new(attrs) }
|
26
19
|
end
|
27
20
|
|
28
21
|
def replace(obj, dont_save = false)
|
@@ -75,6 +68,29 @@ module ActiveRecord
|
|
75
68
|
end
|
76
69
|
@finder_sql << " AND (#{conditions})" if conditions
|
77
70
|
end
|
71
|
+
|
72
|
+
def construct_scope
|
73
|
+
create_scoping = {}
|
74
|
+
set_belongs_to_association_for(create_scoping)
|
75
|
+
{ :create => create_scoping }
|
76
|
+
end
|
77
|
+
|
78
|
+
def new_record(replace_existing)
|
79
|
+
# Make sure we load the target first, if we plan on replacing the existing
|
80
|
+
# instance. Otherwise, if the target has not previously been loaded
|
81
|
+
# elsewhere, the instance we create will get orphaned.
|
82
|
+
load_target if replace_existing
|
83
|
+
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
|
84
|
+
|
85
|
+
if replace_existing
|
86
|
+
replace(record, true)
|
87
|
+
else
|
88
|
+
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
89
|
+
self.target = record
|
90
|
+
end
|
91
|
+
|
92
|
+
record
|
93
|
+
end
|
78
94
|
end
|
79
95
|
end
|
80
96
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module AttributeMethods #:nodoc:
|
3
3
|
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
|
4
|
+
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
4
5
|
|
5
6
|
def self.included(base)
|
6
7
|
base.extend ClassMethods
|
7
8
|
base.attribute_method_suffix *DEFAULT_SUFFIXES
|
9
|
+
base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
|
10
|
+
base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
8
11
|
end
|
9
12
|
|
10
13
|
# Declare and check for suffixed attribute methods.
|
@@ -43,6 +46,68 @@ module ActiveRecord
|
|
43
46
|
@@attribute_method_regexp.match(method_name)
|
44
47
|
end
|
45
48
|
|
49
|
+
|
50
|
+
# Contains the names of the generated attribute methods.
|
51
|
+
def generated_methods #:nodoc:
|
52
|
+
@generated_methods ||= Set.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def generated_methods?
|
56
|
+
!generated_methods.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
# generates all the attribute related methods for columns in the database
|
60
|
+
# accessors, mutators and query methods
|
61
|
+
def define_attribute_methods
|
62
|
+
return if generated_methods?
|
63
|
+
columns_hash.each do |name, column|
|
64
|
+
unless instance_method_already_implemented?(name)
|
65
|
+
if self.serialized_attributes[name]
|
66
|
+
define_read_method_for_serialized_attribute(name)
|
67
|
+
else
|
68
|
+
define_read_method(name.to_sym, name, column)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
unless instance_method_already_implemented?("#{name}=")
|
73
|
+
define_write_method(name.to_sym)
|
74
|
+
end
|
75
|
+
|
76
|
+
unless instance_method_already_implemented?("#{name}?")
|
77
|
+
define_question_method(name)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Check to see if the method is defined in the model or any of its subclasses that also derive from ActiveRecord.
|
83
|
+
# Raise DangerousAttributeError if the method is defined by ActiveRecord though.
|
84
|
+
def instance_method_already_implemented?(method_name)
|
85
|
+
return true if method_name =~ /^id(=$|\?$|$)/
|
86
|
+
@_defined_class_methods ||= Set.new(ancestors.first(ancestors.index(ActiveRecord::Base)).collect! { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.flatten)
|
87
|
+
@@_defined_activerecord_methods ||= Set.new(ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false))
|
88
|
+
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
|
89
|
+
@_defined_class_methods.include?(method_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :define_read_methods :define_attribute_methods
|
93
|
+
|
94
|
+
# +cache_attributes+ allows you to declare which converted attribute values should
|
95
|
+
# be cached. Usually caching only pays off for attributes with expensive conversion
|
96
|
+
# methods, like date columns (e.g. created_at, updated_at).
|
97
|
+
def cache_attributes(*attribute_names)
|
98
|
+
attribute_names.each {|attr| cached_attributes << attr.to_s}
|
99
|
+
end
|
100
|
+
|
101
|
+
# returns the attributes where
|
102
|
+
def cached_attributes
|
103
|
+
@cached_attributes ||=
|
104
|
+
columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
|
105
|
+
end
|
106
|
+
|
107
|
+
def cache_attribute?(attr_name)
|
108
|
+
cached_attributes.include?(attr_name)
|
109
|
+
end
|
110
|
+
|
46
111
|
private
|
47
112
|
# Suffixes a, ?, c become regexp /(a|\?|c)$/
|
48
113
|
def rebuild_attribute_method_regexp
|
@@ -54,9 +119,197 @@ module ActiveRecord
|
|
54
119
|
def attribute_method_suffixes
|
55
120
|
@@attribute_method_suffixes ||= []
|
56
121
|
end
|
122
|
+
|
123
|
+
# Define an attribute reader method. Cope with nil column.
|
124
|
+
def define_read_method(symbol, attr_name, column)
|
125
|
+
cast_code = column.type_cast_code('v') if column
|
126
|
+
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
|
127
|
+
|
128
|
+
unless attr_name.to_s == self.primary_key.to_s
|
129
|
+
access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
|
130
|
+
end
|
131
|
+
|
132
|
+
if cache_attribute?(attr_name)
|
133
|
+
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
|
134
|
+
end
|
135
|
+
evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define read method for serialized attribute.
|
139
|
+
def define_read_method_for_serialized_attribute(attr_name)
|
140
|
+
evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Define an attribute ? method.
|
144
|
+
def define_question_method(attr_name)
|
145
|
+
evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
|
146
|
+
end
|
147
|
+
|
148
|
+
def define_write_method(attr_name)
|
149
|
+
evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
|
150
|
+
end
|
151
|
+
|
152
|
+
# Evaluate the definition for an attribute related method
|
153
|
+
def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
|
154
|
+
|
155
|
+
unless method_name.to_s == primary_key.to_s
|
156
|
+
generated_methods << method_name
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
class_eval(method_definition, __FILE__, __LINE__)
|
161
|
+
rescue SyntaxError => err
|
162
|
+
generated_methods.delete(attr_name)
|
163
|
+
if logger
|
164
|
+
logger.warn "Exception occurred during reader method compilation."
|
165
|
+
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
|
166
|
+
logger.warn "#{err.message}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end # ClassMethods
|
171
|
+
|
172
|
+
|
173
|
+
# Allows access to the object attributes, which are held in the @attributes hash, as though they
|
174
|
+
# were first-class methods. So a Person class with a name attribute can use Person#name and
|
175
|
+
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
176
|
+
# ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
|
177
|
+
# the completed attribute is not nil or 0.
|
178
|
+
#
|
179
|
+
# It's also possible to instantiate related objects, so a Client class belonging to the clients
|
180
|
+
# table with a master_id foreign key can instantiate master through Client#master.
|
181
|
+
def method_missing(method_id, *args, &block)
|
182
|
+
method_name = method_id.to_s
|
183
|
+
|
184
|
+
# If we haven't generated any methods yet, generate them, then
|
185
|
+
# see if we've created the method we're looking for.
|
186
|
+
if !self.class.generated_methods?
|
187
|
+
self.class.define_attribute_methods
|
188
|
+
if self.class.generated_methods.include?(method_name)
|
189
|
+
return self.send(method_id, *args, &block)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
if self.class.primary_key.to_s == method_name
|
194
|
+
id
|
195
|
+
elsif md = self.class.match_attribute_method?(method_name)
|
196
|
+
attribute_name, method_type = md.pre_match, md.to_s
|
197
|
+
if @attributes.include?(attribute_name)
|
198
|
+
__send__("attribute#{method_type}", attribute_name, *args, &block)
|
199
|
+
else
|
200
|
+
super
|
201
|
+
end
|
202
|
+
elsif @attributes.include?(method_name)
|
203
|
+
read_attribute(method_name)
|
204
|
+
else
|
205
|
+
super
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
210
|
+
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
211
|
+
def read_attribute(attr_name)
|
212
|
+
attr_name = attr_name.to_s
|
213
|
+
if !(value = @attributes[attr_name]).nil?
|
214
|
+
if column = column_for_attribute(attr_name)
|
215
|
+
if unserializable_attribute?(attr_name, column)
|
216
|
+
unserialize_attribute(attr_name)
|
217
|
+
else
|
218
|
+
column.type_cast(value)
|
219
|
+
end
|
220
|
+
else
|
221
|
+
value
|
222
|
+
end
|
223
|
+
else
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def read_attribute_before_type_cast(attr_name)
|
229
|
+
@attributes[attr_name]
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns true if the attribute is of a text column and marked for serialization.
|
233
|
+
def unserializable_attribute?(attr_name, column)
|
234
|
+
column.text? && self.class.serialized_attributes[attr_name]
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the unserialized object of the attribute.
|
238
|
+
def unserialize_attribute(attr_name)
|
239
|
+
unserialized_object = object_from_yaml(@attributes[attr_name])
|
240
|
+
|
241
|
+
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
|
242
|
+
@attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
|
243
|
+
else
|
244
|
+
raise SerializationTypeMismatch,
|
245
|
+
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
251
|
+
# columns are turned into nil.
|
252
|
+
def write_attribute(attr_name, value)
|
253
|
+
attr_name = attr_name.to_s
|
254
|
+
@attributes_cache.delete(attr_name)
|
255
|
+
if (column = column_for_attribute(attr_name)) && column.number?
|
256
|
+
@attributes[attr_name] = convert_number_column_value(value)
|
257
|
+
else
|
258
|
+
@attributes[attr_name] = value
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def query_attribute(attr_name)
|
264
|
+
unless value = read_attribute(attr_name)
|
265
|
+
false
|
266
|
+
else
|
267
|
+
column = self.class.columns_hash[attr_name]
|
268
|
+
if column.nil?
|
269
|
+
if Numeric === value || value !~ /[^0-9]/
|
270
|
+
!value.to_i.zero?
|
271
|
+
else
|
272
|
+
!value.blank?
|
273
|
+
end
|
274
|
+
elsif column.number?
|
275
|
+
!value.zero?
|
276
|
+
else
|
277
|
+
!value.blank?
|
278
|
+
end
|
279
|
+
end
|
57
280
|
end
|
281
|
+
|
282
|
+
# A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
|
283
|
+
# person.respond_to?("name?") which will all return true.
|
284
|
+
alias :respond_to_without_attributes? :respond_to?
|
285
|
+
def respond_to?(method, include_priv = false)
|
286
|
+
method_name = method.to_s
|
287
|
+
if super
|
288
|
+
return true
|
289
|
+
elsif !self.class.generated_methods?
|
290
|
+
self.class.define_attribute_methods
|
291
|
+
if self.class.generated_methods.include?(method_name)
|
292
|
+
return true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
if @attributes.nil?
|
297
|
+
return super
|
298
|
+
elsif @attributes.include?(method_name)
|
299
|
+
return true
|
300
|
+
elsif md = self.class.match_attribute_method?(method_name)
|
301
|
+
return true if @attributes.include?(md.pre_match)
|
302
|
+
end
|
303
|
+
super
|
304
|
+
end
|
305
|
+
|
58
306
|
|
59
307
|
private
|
308
|
+
|
309
|
+
def missing_attribute(attr_name, stack)
|
310
|
+
raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
|
311
|
+
end
|
312
|
+
|
60
313
|
# Handle *? for method_missing.
|
61
314
|
def attribute?(attribute_name)
|
62
315
|
query_attribute(attribute_name)
|