activerecord 1.0.0 → 4.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +213 -0
- data/examples/performance.rb +172 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +180 -84
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +92 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
- data/lib/active_record/associations/has_many_association.rb +116 -85
- data/lib/active_record/associations/has_many_through_association.rb +197 -0
- data/lib/active_record/associations/has_one_association.rb +102 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +1437 -431
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
- data/lib/active_record/attribute_methods/dirty.rb +118 -0
- data/lib/active_record/attribute_methods/primary_key.rb +122 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +107 -0
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
- data/lib/active_record/attribute_methods/write.rb +63 -0
- data/lib/active_record/attribute_methods.rb +393 -0
- data/lib/active_record/autosave_association.rb +426 -0
- data/lib/active_record/base.rb +268 -930
- data/lib/active_record/callbacks.rb +203 -230
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +122 -0
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +213 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +892 -138
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +181 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +82 -0
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +1015 -0
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +546 -0
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +509 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +205 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +402 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +544 -87
- data/lib/active_record/relation/batches.rb +93 -0
- data/lib/active_record/relation/calculations.rb +399 -0
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +349 -0
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +106 -0
- data/lib/active_record/relation/query_methods.rb +1044 -0
- data/lib/active_record/relation/spawn_methods.rb +73 -0
- data/lib/active_record/relation.rb +655 -0
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +65 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +197 -0
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +96 -0
- data/lib/active_record/timestamp.rb +119 -0
- data/lib/active_record/transactions.rb +366 -69
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +49 -0
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +225 -0
- data/lib/active_record/validations.rb +64 -185
- data/lib/active_record/version.rb +11 -0
- data/lib/active_record.rb +149 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record.rb +23 -0
- metadata +261 -161
- data/CHANGELOG +0 -581
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.png +0 -0
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/associations/association_collection.rb +0 -70
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/observer.rb +0 -71
- 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/mysql.rb +0 -1117
- 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/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- 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/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- 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/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- 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/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -0,0 +1,201 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module AttributeAssignment
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ActiveModel::DeprecatedMassAssignmentSecurity
|
6
|
+
include ActiveModel::ForbiddenAttributesProtection
|
7
|
+
|
8
|
+
# Allows you to set all the attributes by passing in a hash of attributes with
|
9
|
+
# keys matching the attribute names (which again matches the column names).
|
10
|
+
#
|
11
|
+
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
|
+
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
+
# exception is raised.
|
14
|
+
def assign_attributes(new_attributes)
|
15
|
+
return if new_attributes.blank?
|
16
|
+
|
17
|
+
attributes = new_attributes.stringify_keys
|
18
|
+
multi_parameter_attributes = []
|
19
|
+
nested_parameter_attributes = []
|
20
|
+
|
21
|
+
attributes = sanitize_for_mass_assignment(attributes)
|
22
|
+
|
23
|
+
attributes.each do |k, v|
|
24
|
+
if k.include?("(")
|
25
|
+
multi_parameter_attributes << [ k, v ]
|
26
|
+
elsif v.is_a?(Hash)
|
27
|
+
nested_parameter_attributes << [ k, v ]
|
28
|
+
else
|
29
|
+
_assign_attribute(k, v)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
34
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
alias attributes= assign_attributes
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def _assign_attribute(k, v)
|
42
|
+
public_send("#{k}=", v)
|
43
|
+
rescue NoMethodError
|
44
|
+
if respond_to?("#{k}=")
|
45
|
+
raise
|
46
|
+
else
|
47
|
+
raise UnknownAttributeError, "unknown attribute: #{k}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
52
|
+
def assign_nested_parameter_attributes(pairs)
|
53
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
57
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
58
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
59
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
60
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
|
61
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
62
|
+
def assign_multiparameter_attributes(pairs)
|
63
|
+
execute_callstack_for_multiparameter_attributes(
|
64
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
69
|
+
errors = []
|
70
|
+
callstack.each do |name, values_with_empty_parameters|
|
71
|
+
begin
|
72
|
+
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
|
73
|
+
rescue => ex
|
74
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
unless errors.empty?
|
78
|
+
error_descriptions = errors.map { |ex| ex.message }.join(",")
|
79
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
84
|
+
attributes = {}
|
85
|
+
|
86
|
+
pairs.each do |(multiparameter_name, value)|
|
87
|
+
attribute_name = multiparameter_name.split("(").first
|
88
|
+
attributes[attribute_name] ||= {}
|
89
|
+
|
90
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
91
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
92
|
+
end
|
93
|
+
|
94
|
+
attributes
|
95
|
+
end
|
96
|
+
|
97
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
98
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_parameter_position(multiparameter_name)
|
102
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
class MultiparameterAttribute #:nodoc:
|
106
|
+
attr_reader :object, :name, :values, :column
|
107
|
+
|
108
|
+
def initialize(object, name, values)
|
109
|
+
@object = object
|
110
|
+
@name = name
|
111
|
+
@values = values
|
112
|
+
end
|
113
|
+
|
114
|
+
def read_value
|
115
|
+
return if values.values.compact.empty?
|
116
|
+
|
117
|
+
@column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
|
118
|
+
klass = column.klass
|
119
|
+
|
120
|
+
if klass == Time
|
121
|
+
read_time
|
122
|
+
elsif klass == Date
|
123
|
+
read_date
|
124
|
+
else
|
125
|
+
read_other(klass)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def instantiate_time_object(set_values)
|
132
|
+
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
|
133
|
+
Time.zone.local(*set_values)
|
134
|
+
else
|
135
|
+
Time.send(object.class.default_timezone, *set_values)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_time
|
140
|
+
# If column is a :time (and not :date or :timestamp) there is no need to validate if
|
141
|
+
# there are year/month/day fields
|
142
|
+
if column.type == :time
|
143
|
+
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
144
|
+
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
145
|
+
values[key] ||= value
|
146
|
+
end
|
147
|
+
else
|
148
|
+
# else column is a timestamp, so if Date bits were not provided, error
|
149
|
+
validate_required_parameters!([1,2,3])
|
150
|
+
|
151
|
+
# If Date bits were provided but blank, then return nil
|
152
|
+
return if blank_date_parameter?
|
153
|
+
end
|
154
|
+
|
155
|
+
max_position = extract_max_param(6)
|
156
|
+
set_values = values.values_at(*(1..max_position))
|
157
|
+
# If Time bits are not there, then default to 0
|
158
|
+
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
|
159
|
+
instantiate_time_object(set_values)
|
160
|
+
end
|
161
|
+
|
162
|
+
def read_date
|
163
|
+
return if blank_date_parameter?
|
164
|
+
set_values = values.values_at(1,2,3)
|
165
|
+
begin
|
166
|
+
Date.new(*set_values)
|
167
|
+
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
168
|
+
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def read_other(klass)
|
173
|
+
max_position = extract_max_param
|
174
|
+
positions = (1..max_position)
|
175
|
+
validate_required_parameters!(positions)
|
176
|
+
|
177
|
+
set_values = values.values_at(*positions)
|
178
|
+
klass.new(*set_values)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Checks whether some blank date parameter exists. Note that this is different
|
182
|
+
# than the validate_required_parameters! method, since it just checks for blank
|
183
|
+
# positions instead of missing ones, and does not raise in case one blank position
|
184
|
+
# exists. The caller is responsible to handle the case of this returning true.
|
185
|
+
def blank_date_parameter?
|
186
|
+
(1..3).any? { |position| values[position].blank? }
|
187
|
+
end
|
188
|
+
|
189
|
+
# If some position is not provided, it errors out a missing parameter exception.
|
190
|
+
def validate_required_parameters!(positions)
|
191
|
+
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
192
|
+
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def extract_max_param(upper_cap = 100)
|
197
|
+
[values.keys.max, upper_cap].min
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
# = Active Record Attribute Methods Before Type Cast
|
4
|
+
#
|
5
|
+
# <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
|
6
|
+
# read the value of the attributes before typecasting and deserialization.
|
7
|
+
#
|
8
|
+
# class Task < ActiveRecord::Base
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
12
|
+
# task.id # => 1
|
13
|
+
# task.completed_on # => Sun, 21 Oct 2012
|
14
|
+
#
|
15
|
+
# task.attributes_before_type_cast
|
16
|
+
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
|
17
|
+
# task.read_attribute_before_type_cast('id') # => "1"
|
18
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
19
|
+
#
|
20
|
+
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
|
21
|
+
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
|
22
|
+
# suffix.
|
23
|
+
#
|
24
|
+
# task.id_before_type_cast # => "1"
|
25
|
+
# task.completed_on_before_type_cast # => "2012-10-21"
|
26
|
+
module BeforeTypeCast
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
included do
|
30
|
+
attribute_method_suffix "_before_type_cast"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the value of the attribute identified by +attr_name+ before
|
34
|
+
# typecasting and deserialization.
|
35
|
+
#
|
36
|
+
# class Task < ActiveRecord::Base
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
40
|
+
# task.read_attribute('id') # => 1
|
41
|
+
# task.read_attribute_before_type_cast('id') # => '1'
|
42
|
+
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
|
43
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
44
|
+
def read_attribute_before_type_cast(attr_name)
|
45
|
+
@attributes[attr_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a hash of attributes before typecasting and deserialization.
|
49
|
+
#
|
50
|
+
# class Task < ActiveRecord::Base
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
|
54
|
+
# task.attributes
|
55
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
|
56
|
+
# task.attributes_before_type_cast
|
57
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
58
|
+
def attributes_before_type_cast
|
59
|
+
@attributes
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Handle *_before_type_cast for method_missing.
|
65
|
+
def attribute_before_type_cast(attribute_name)
|
66
|
+
read_attribute_before_type_cast(attribute_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeMethods
|
5
|
+
module Dirty # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
include ActiveModel::Dirty
|
9
|
+
|
10
|
+
included do
|
11
|
+
if self < ::ActiveRecord::Timestamp
|
12
|
+
raise "You cannot include Dirty after Timestamp"
|
13
|
+
end
|
14
|
+
|
15
|
+
class_attribute :partial_writes, instance_writer: false
|
16
|
+
self.partial_writes = true
|
17
|
+
|
18
|
+
def self.partial_updates=(v); self.partial_writes = v; end
|
19
|
+
def self.partial_updates?; partial_writes?; end
|
20
|
+
def self.partial_updates; partial_writes; end
|
21
|
+
|
22
|
+
ActiveSupport::Deprecation.deprecate_methods(
|
23
|
+
singleton_class,
|
24
|
+
:partial_updates= => :partial_writes=,
|
25
|
+
:partial_updates? => :partial_writes?,
|
26
|
+
:partial_updates => :partial_writes
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attempts to +save+ the record and clears changed attributes if successful.
|
31
|
+
def save(*)
|
32
|
+
if status = super
|
33
|
+
@previously_changed = changes
|
34
|
+
@changed_attributes.clear
|
35
|
+
end
|
36
|
+
status
|
37
|
+
end
|
38
|
+
|
39
|
+
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
40
|
+
def save!(*)
|
41
|
+
super.tap do
|
42
|
+
@previously_changed = changes
|
43
|
+
@changed_attributes.clear
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# <tt>reload</tt> the record and clears changed attributes.
|
48
|
+
def reload(*)
|
49
|
+
super.tap do
|
50
|
+
@previously_changed.clear
|
51
|
+
@changed_attributes.clear
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
# Wrap write_attribute to remember original attribute value.
|
57
|
+
def write_attribute(attr, value)
|
58
|
+
attr = attr.to_s
|
59
|
+
|
60
|
+
# The attribute already has an unsaved change.
|
61
|
+
if attribute_changed?(attr)
|
62
|
+
old = @changed_attributes[attr]
|
63
|
+
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
|
64
|
+
else
|
65
|
+
old = clone_attribute_value(:read_attribute, attr)
|
66
|
+
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Carry on.
|
70
|
+
super(attr, value)
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_record(*)
|
74
|
+
partial_writes? ? super(keys_for_partial_write) : super
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_record(*)
|
78
|
+
partial_writes? ? super(keys_for_partial_write) : super
|
79
|
+
end
|
80
|
+
|
81
|
+
# Serialized attributes should always be written in case they've been
|
82
|
+
# changed in place.
|
83
|
+
def keys_for_partial_write
|
84
|
+
changed | (attributes.keys & self.class.serialized_attributes.keys)
|
85
|
+
end
|
86
|
+
|
87
|
+
def _field_changed?(attr, old, value)
|
88
|
+
if column = column_for_attribute(attr)
|
89
|
+
if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
|
90
|
+
changes_from_zero_to_string?(old, value))
|
91
|
+
value = nil
|
92
|
+
else
|
93
|
+
value = column.type_cast(value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
old != value
|
98
|
+
end
|
99
|
+
|
100
|
+
def changes_from_nil_to_empty_string?(column, old, value)
|
101
|
+
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
102
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
103
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
104
|
+
# be typecast back to 0 (''.to_i => 0)
|
105
|
+
column.null && (old.nil? || old == 0) && value.blank?
|
106
|
+
end
|
107
|
+
|
108
|
+
def changes_from_zero_to_string?(old, value)
|
109
|
+
# For columns with old 0 and value non-empty string
|
110
|
+
old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def non_zero?(value)
|
114
|
+
value !~ /\A0+(\.0+)?\z/
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeMethods
|
5
|
+
module PrimaryKey
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Returns this record's primary key value wrapped in an Array if one is
|
9
|
+
# available.
|
10
|
+
def to_key
|
11
|
+
sync_with_transaction_state
|
12
|
+
key = self.id
|
13
|
+
[key] if key
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the primary key value.
|
17
|
+
def id
|
18
|
+
sync_with_transaction_state
|
19
|
+
read_attribute(self.class.primary_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the primary key value.
|
23
|
+
def id=(value)
|
24
|
+
sync_with_transaction_state
|
25
|
+
write_attribute(self.class.primary_key, value) if self.class.primary_key
|
26
|
+
end
|
27
|
+
|
28
|
+
# Queries the primary key value.
|
29
|
+
def id?
|
30
|
+
sync_with_transaction_state
|
31
|
+
query_attribute(self.class.primary_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the primary key value before type cast.
|
35
|
+
def id_before_type_cast
|
36
|
+
sync_with_transaction_state
|
37
|
+
read_attribute_before_type_cast(self.class.primary_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def attribute_method?(attr_name)
|
43
|
+
attr_name == 'id' || super
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
def define_method_attribute(attr_name)
|
48
|
+
super
|
49
|
+
|
50
|
+
if attr_name == primary_key && attr_name != 'id'
|
51
|
+
generated_attribute_methods.send(:alias_method, :id, primary_key)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
|
56
|
+
|
57
|
+
def dangerous_attribute_method?(method_name)
|
58
|
+
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Defines the primary key field -- can be overridden in subclasses.
|
62
|
+
# Overwriting will negate any effect of the +primary_key_prefix_type+
|
63
|
+
# setting, though.
|
64
|
+
def primary_key
|
65
|
+
@primary_key = reset_primary_key unless defined? @primary_key
|
66
|
+
@primary_key
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a quoted version of the primary key name, used to construct
|
70
|
+
# SQL statements.
|
71
|
+
def quoted_primary_key
|
72
|
+
@quoted_primary_key ||= connection.quote_column_name(primary_key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset_primary_key #:nodoc:
|
76
|
+
if self == base_class
|
77
|
+
self.primary_key = get_primary_key(base_class.name)
|
78
|
+
else
|
79
|
+
self.primary_key = base_class.primary_key
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_primary_key(base_name) #:nodoc:
|
84
|
+
return 'id' if base_name.blank?
|
85
|
+
|
86
|
+
case primary_key_prefix_type
|
87
|
+
when :table_name
|
88
|
+
base_name.foreign_key(false)
|
89
|
+
when :table_name_with_underscore
|
90
|
+
base_name.foreign_key
|
91
|
+
else
|
92
|
+
if ActiveRecord::Base != self && table_exists?
|
93
|
+
connection.schema_cache.primary_keys(table_name)
|
94
|
+
else
|
95
|
+
'id'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Sets the name of the primary key column.
|
101
|
+
#
|
102
|
+
# class Project < ActiveRecord::Base
|
103
|
+
# self.primary_key = 'sysid'
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# You can also define the +primary_key+ method yourself:
|
107
|
+
#
|
108
|
+
# class Project < ActiveRecord::Base
|
109
|
+
# def self.primary_key
|
110
|
+
# 'foo_' + super
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# Project.primary_key # => "foo_id"
|
115
|
+
def primary_key=(value)
|
116
|
+
@primary_key = value && value.to_s
|
117
|
+
@quoted_primary_key = nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Query
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attribute_method_suffix "?"
|
8
|
+
end
|
9
|
+
|
10
|
+
def query_attribute(attr_name)
|
11
|
+
value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
12
|
+
|
13
|
+
case value
|
14
|
+
when true then true
|
15
|
+
when false, nil then false
|
16
|
+
else
|
17
|
+
column = self.class.columns_hash[attr_name]
|
18
|
+
if column.nil?
|
19
|
+
if Numeric === value || value !~ /[^0-9]/
|
20
|
+
!value.to_i.zero?
|
21
|
+
else
|
22
|
+
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
23
|
+
!value.blank?
|
24
|
+
end
|
25
|
+
elsif column.number?
|
26
|
+
!value.zero?
|
27
|
+
else
|
28
|
+
!value.blank?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
# Handle *? for method_missing.
|
35
|
+
def attribute?(attribute_name)
|
36
|
+
query_attribute(attribute_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Read
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :attribute_types_cached_by_default, instance_writer: false
|
10
|
+
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# +cache_attributes+ allows you to declare which converted attribute
|
15
|
+
# values should be cached. Usually caching only pays off for attributes
|
16
|
+
# with expensive conversion methods, like time related columns (e.g.
|
17
|
+
# +created_at+, +updated_at+).
|
18
|
+
def cache_attributes(*attribute_names)
|
19
|
+
cached_attributes.merge attribute_names.map { |attr| attr.to_s }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the attributes which are cached. By default time related columns
|
23
|
+
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
|
24
|
+
def cached_attributes
|
25
|
+
@cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns +true+ if the provided attribute is being cached.
|
29
|
+
def cache_attribute?(attr_name)
|
30
|
+
cached_attributes.include?(attr_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# We want to generate the methods via module_eval rather than
|
36
|
+
# define_method, because define_method is slower on dispatch and
|
37
|
+
# uses more memory (because it creates a closure).
|
38
|
+
#
|
39
|
+
# But sometimes the database might return columns with
|
40
|
+
# characters that are not allowed in normal method names (like
|
41
|
+
# 'my_column(omg)'. So to work around this we first define with
|
42
|
+
# the __temp__ identifier, and then use alias method to rename
|
43
|
+
# it to what we want.
|
44
|
+
#
|
45
|
+
# We are also defining a constant to hold the frozen string of
|
46
|
+
# the attribute name. Using a constant means that we do not have
|
47
|
+
# to allocate an object on each call to the attribute method.
|
48
|
+
# Making it frozen means that it doesn't get duped when used to
|
49
|
+
# key the @attributes_cache in read_attribute.
|
50
|
+
def define_method_attribute(name)
|
51
|
+
safe_name = name.unpack('h*').first
|
52
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
53
|
+
def __temp__#{safe_name}
|
54
|
+
read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
|
55
|
+
end
|
56
|
+
alias_method #{name.inspect}, :__temp__#{safe_name}
|
57
|
+
undef_method :__temp__#{safe_name}
|
58
|
+
STR
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def cacheable_column?(column)
|
64
|
+
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
65
|
+
! serialized_attributes.include? column.name
|
66
|
+
else
|
67
|
+
attribute_types_cached_by_default.include?(column.type)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after
|
73
|
+
# it has been typecast (for example, "2004-12-12" in a data column is cast
|
74
|
+
# to a date object, like Date.new(2004, 12, 12)).
|
75
|
+
def read_attribute(attr_name)
|
76
|
+
# If it's cached, just return it
|
77
|
+
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
|
78
|
+
name = attr_name.to_s
|
79
|
+
@attributes_cache[name] || @attributes_cache.fetch(name) {
|
80
|
+
column = @columns_hash.fetch(name) {
|
81
|
+
return @attributes.fetch(name) {
|
82
|
+
if name == 'id' && self.class.primary_key != name
|
83
|
+
read_attribute(self.class.primary_key)
|
84
|
+
end
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
value = @attributes.fetch(name) {
|
89
|
+
return block_given? ? yield(name) : nil
|
90
|
+
}
|
91
|
+
|
92
|
+
if self.class.cache_attribute?(name)
|
93
|
+
@attributes_cache[name] = column.type_cast(value)
|
94
|
+
else
|
95
|
+
column.type_cast value
|
96
|
+
end
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def attribute(attribute_name)
|
103
|
+
read_attribute(attribute_name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|