activerecord 1.0.0 → 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 +4928 -3
- data/README +45 -46
- data/RUNNING_UNIT_TESTS +8 -11
- data/Rakefile +247 -0
- data/install.rb +8 -38
- data/lib/active_record/aggregations.rb +64 -49
- data/lib/active_record/associations/association_collection.rb +217 -47
- data/lib/active_record/associations/association_proxy.rb +159 -0
- data/lib/active_record/associations/belongs_to_association.rb +56 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
- data/lib/active_record/associations/has_many_association.rb +145 -75
- data/lib/active_record/associations/has_many_through_association.rb +283 -0
- data/lib/active_record/associations/has_one_association.rb +96 -0
- data/lib/active_record/associations.rb +1537 -304
- data/lib/active_record/attribute_methods.rb +328 -0
- data/lib/active_record/base.rb +2001 -588
- data/lib/active_record/calculations.rb +269 -0
- data/lib/active_record/callbacks.rb +169 -165
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
- data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
- data/lib/active_record/fixtures.rb +946 -100
- data/lib/active_record/locking/optimistic.rb +144 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +417 -0
- data/lib/active_record/observer.rb +142 -32
- data/lib/active_record/query_cache.rb +23 -0
- data/lib/active_record/reflection.rb +163 -70
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +171 -0
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/serializers/xml_serializer.rb +315 -0
- data/lib/active_record/timestamp.rb +41 -0
- data/lib/active_record/transactions.rb +87 -57
- data/lib/active_record/validations.rb +909 -122
- data/lib/active_record/vendor/db2.rb +362 -0
- data/lib/active_record/vendor/mysql.rb +126 -29
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +35 -7
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +72 -0
- data/test/abstract_unit.rb +73 -5
- data/test/active_schema_test_mysql.rb +43 -0
- data/test/adapter_test.rb +105 -0
- data/test/adapter_test_sqlserver.rb +95 -0
- data/test/aggregations_test.rb +110 -16
- data/test/all.sh +2 -2
- data/test/ar_schema_test.rb +33 -0
- data/test/association_inheritance_reload.rb +14 -0
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +147 -0
- data/test/associations/cascaded_eager_loading_test.rb +110 -0
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +442 -0
- data/test/associations/extension_test.rb +47 -0
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +553 -0
- data/test/associations_test.rb +1930 -267
- data/test/attribute_methods_test.rb +146 -0
- data/test/base_test.rb +1316 -84
- data/test/binary_test.rb +32 -0
- data/test/calculations_test.rb +251 -0
- data/test/callbacks_test.rb +400 -0
- data/test/class_inheritable_attributes_test.rb +3 -4
- data/test/column_alias_test.rb +17 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_db2/connection.rb +25 -0
- data/test/connections/native_firebird/connection.rb +26 -0
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +21 -18
- data/test/connections/native_openbase/connection.rb +21 -0
- data/test/connections/native_oracle/connection.rb +27 -0
- data/test/connections/native_postgresql/connection.rb +17 -18
- data/test/connections/native_sqlite/connection.rb +17 -16
- data/test/connections/native_sqlite3/connection.rb +25 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/copy_table_test_sqlite.rb +69 -0
- data/test/datatype_test_postgresql.rb +203 -0
- data/test/date_time_test.rb +37 -0
- data/test/default_test_firebird.rb +16 -0
- data/test/defaults_test.rb +67 -0
- data/test/deprecated_finder_test.rb +30 -0
- data/test/finder_test.rb +607 -32
- data/test/fixtures/accounts.yml +28 -0
- 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 +107 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
- data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
- data/test/fixtures/bad_fixtures/blank_line +3 -0
- data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
- data/test/fixtures/bad_fixtures/missing_value +1 -0
- data/test/fixtures/binaries.yml +132 -0
- data/test/fixtures/binary.rb +2 -0
- data/test/fixtures/book.rb +4 -0
- data/test/fixtures/books.yml +7 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/categories_posts.yml +23 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +17 -0
- data/test/fixtures/category.rb +26 -0
- data/test/fixtures/citation.rb +6 -0
- data/test/fixtures/comment.rb +23 -0
- data/test/fixtures/comments.yml +59 -0
- data/test/fixtures/companies.yml +55 -0
- data/test/fixtures/company.rb +81 -4
- data/test/fixtures/company_in_module.rb +32 -6
- data/test/fixtures/computer.rb +4 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customer.rb +28 -3
- data/test/fixtures/customers.yml +17 -0
- data/test/fixtures/db_definitions/db2.drop.sql +33 -0
- data/test/fixtures/db_definitions/db2.sql +235 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +5 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
- data/test/fixtures/db_definitions/firebird.sql +310 -0
- data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
- data/test/fixtures/db_definitions/firebird2.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
- data/test/fixtures/db_definitions/frontbase.sql +273 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +318 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
- data/test/fixtures/db_definitions/oracle.sql +330 -0
- data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle2.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
- data/test/fixtures/db_definitions/postgresql.sql +217 -38
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +2 -2
- data/test/fixtures/db_definitions/schema.rb +354 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
- data/test/fixtures/db_definitions/sqlite.sql +139 -5
- data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite2.sql +1 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
- data/test/fixtures/db_definitions/sybase.sql +222 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developer.rb +70 -6
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects/david_action_controller +2 -1
- data/test/fixtures/developers_projects/david_active_record +2 -1
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/funny_jokes.yml +10 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +3 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixed_case_monkey.rb +3 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixins.yml +29 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/order.rb +4 -0
- 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/people.yml +3 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +59 -0
- data/test/fixtures/posts.yml +48 -0
- data/test/fixtures/project.rb +27 -2
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +18 -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/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +4 -3
- data/test/fixtures/tag.rb +7 -0
- data/test/fixtures/tagging.rb +10 -0
- data/test/fixtures/taggings.yml +25 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/task.rb +3 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures/topic.rb +20 -3
- data/test/fixtures/topics.yml +22 -0
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +574 -8
- data/test/inheritance_test.rb +113 -27
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +56 -29
- data/test/locking_test.rb +273 -0
- data/test/method_scoping_test.rb +416 -0
- data/test/migration_test.rb +933 -0
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_test.rb +95 -0
- data/test/modules_test.rb +23 -10
- data/test/multiple_db_test.rb +17 -3
- data/test/pk_test.rb +59 -15
- data/test/query_cache_test.rb +104 -0
- data/test/readonly_test.rb +107 -0
- data/test/reflection_test.rb +124 -27
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +131 -0
- data/test/schema_test_postgresql.rb +64 -0
- data/test/serialization_test.rb +47 -0
- data/test/synonym_test_oracle.rb +17 -0
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +48 -0
- data/test/transactions_test.rb +227 -29
- data/test/unconnected_test.rb +14 -6
- data/test/validations_test.rb +1293 -32
- data/test/xml_serialization_test.rb +202 -0
- metadata +347 -143
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/deprecated_associations_test.rb +0 -336
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/inflector_test.rb +0 -104
- data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,328 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods #:nodoc:
|
3
|
+
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
|
4
|
+
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
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
|
11
|
+
end
|
12
|
+
|
13
|
+
# Declare and check for suffixed attribute methods.
|
14
|
+
module ClassMethods
|
15
|
+
# Declare a method available for all attributes with the given suffix.
|
16
|
+
# Uses method_missing and respond_to? to rewrite the method
|
17
|
+
# #{attr}#{suffix}(*args, &block)
|
18
|
+
# to
|
19
|
+
# attribute#{suffix}(#{attr}, *args, &block)
|
20
|
+
#
|
21
|
+
# An attribute#{suffix} instance method must exist and accept at least
|
22
|
+
# the attr argument.
|
23
|
+
#
|
24
|
+
# For example:
|
25
|
+
# class Person < ActiveRecord::Base
|
26
|
+
# attribute_method_suffix '_changed?'
|
27
|
+
#
|
28
|
+
# private
|
29
|
+
# def attribute_changed?(attr)
|
30
|
+
# ...
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# person = Person.find(1)
|
35
|
+
# person.name_changed? # => false
|
36
|
+
# person.name = 'Hubert'
|
37
|
+
# person.name_changed? # => true
|
38
|
+
def attribute_method_suffix(*suffixes)
|
39
|
+
attribute_method_suffixes.concat suffixes
|
40
|
+
rebuild_attribute_method_regexp
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns MatchData if method_name is an attribute method.
|
44
|
+
def match_attribute_method?(method_name)
|
45
|
+
rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
|
46
|
+
@@attribute_method_regexp.match(method_name)
|
47
|
+
end
|
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
|
+
|
111
|
+
private
|
112
|
+
# Suffixes a, ?, c become regexp /(a|\?|c)$/
|
113
|
+
def rebuild_attribute_method_regexp
|
114
|
+
suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
|
115
|
+
@@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
|
116
|
+
end
|
117
|
+
|
118
|
+
# Default to =, ?, _before_type_cast
|
119
|
+
def attribute_method_suffixes
|
120
|
+
@@attribute_method_suffixes ||= []
|
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
|
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
|
+
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
def missing_attribute(attr_name, stack)
|
310
|
+
raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
|
311
|
+
end
|
312
|
+
|
313
|
+
# Handle *? for method_missing.
|
314
|
+
def attribute?(attribute_name)
|
315
|
+
query_attribute(attribute_name)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Handle *= for method_missing.
|
319
|
+
def attribute=(attribute_name, value)
|
320
|
+
write_attribute(attribute_name, value)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Handle *_before_type_cast for method_missing.
|
324
|
+
def attribute_before_type_cast(attribute_name)
|
325
|
+
read_attribute_before_type_cast(attribute_name)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|