activerecord 5.2.3
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 +937 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +217 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record.rb +188 -0
- data/lib/active_record/aggregations.rb +283 -0
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations.rb +1860 -0
- data/lib/active_record/associations/alias_tracker.rb +81 -0
- data/lib/active_record/associations/association.rb +299 -0
- data/lib/active_record/associations/association_scope.rb +168 -0
- data/lib/active_record/associations/belongs_to_association.rb +130 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +140 -0
- data/lib/active_record/associations/builder/belongs_to.rb +163 -0
- data/lib/active_record/associations/builder/collection_association.rb +82 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
- data/lib/active_record/associations/builder/has_many.rb +17 -0
- data/lib/active_record/associations/builder/has_one.rb +30 -0
- data/lib/active_record/associations/builder/singular_association.rb +42 -0
- data/lib/active_record/associations/collection_association.rb +513 -0
- data/lib/active_record/associations/collection_proxy.rb +1131 -0
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +144 -0
- data/lib/active_record/associations/has_many_through_association.rb +227 -0
- data/lib/active_record/associations/has_one_association.rb +120 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency.rb +262 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +193 -0
- data/lib/active_record/associations/preloader/association.rb +131 -0
- data/lib/active_record/associations/preloader/through_association.rb +107 -0
- data/lib/active_record/associations/singular_association.rb +73 -0
- data/lib/active_record/associations/through_association.rb +121 -0
- data/lib/active_record/attribute_assignment.rb +88 -0
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods.rb +492 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_record/attribute_methods/dirty.rb +150 -0
- data/lib/active_record/attribute_methods/primary_key.rb +143 -0
- data/lib/active_record/attribute_methods/query.rb +42 -0
- data/lib/active_record/attribute_methods/read.rb +85 -0
- data/lib/active_record/attribute_methods/serialization.rb +90 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_record/attribute_methods/write.rb +68 -0
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +498 -0
- data/lib/active_record/base.rb +329 -0
- data/lib/active_record/callbacks.rb +353 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +50 -0
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
- data/lib/active_record/connection_adapters/column.rb +91 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +218 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +122 -0
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +380 -0
- data/lib/active_record/explain.rb +50 -0
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +1065 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +283 -0
- data/lib/active_record/integration.rb +155 -0
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +198 -0
- data/lib/active_record/locking/pessimistic.rb +89 -0
- data/lib/active_record/log_subscriber.rb +137 -0
- data/lib/active_record/migration.rb +1378 -0
- data/lib/active_record/migration/command_recorder.rb +240 -0
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/model_schema.rb +521 -0
- data/lib/active_record/nested_attributes.rb +600 -0
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +763 -0
- data/lib/active_record/query_cache.rb +45 -0
- data/lib/active_record/querying.rb +70 -0
- data/lib/active_record/railtie.rb +226 -0
- data/lib/active_record/railties/console_sandbox.rb +7 -0
- data/lib/active_record/railties/controller_runtime.rb +56 -0
- data/lib/active_record/railties/databases.rake +377 -0
- data/lib/active_record/readonly_attributes.rb +24 -0
- data/lib/active_record/reflection.rb +1044 -0
- data/lib/active_record/relation.rb +629 -0
- data/lib/active_record/relation/batches.rb +287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +417 -0
- data/lib/active_record/relation/delegation.rb +147 -0
- data/lib/active_record/relation/finder_methods.rb +565 -0
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder.rb +152 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1231 -0
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +77 -0
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/result.rb +149 -0
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +222 -0
- data/lib/active_record/schema.rb +70 -0
- data/lib/active_record/schema_dumper.rb +255 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +106 -0
- data/lib/active_record/scoping/default.rb +152 -0
- data/lib/active_record/scoping/named.rb +213 -0
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +211 -0
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +153 -0
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +502 -0
- data/lib/active_record/translation.rb +24 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/validations.rb +93 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +60 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +238 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration.rb +35 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- metadata +333 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
# = Active Record Through Association
|
6
|
+
module ThroughAssociation #:nodoc:
|
7
|
+
delegate :source_reflection, to: :reflection
|
8
|
+
|
9
|
+
private
|
10
|
+
def through_reflection
|
11
|
+
@through_reflection ||= begin
|
12
|
+
refl = reflection.through_reflection
|
13
|
+
|
14
|
+
while refl.through_reflection?
|
15
|
+
refl = refl.through_reflection
|
16
|
+
end
|
17
|
+
|
18
|
+
refl
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def through_association
|
23
|
+
@through_association ||= owner.association(through_reflection.name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# We merge in these scopes for two reasons:
|
27
|
+
#
|
28
|
+
# 1. To get the default_scope conditions for any of the other reflections in the chain
|
29
|
+
# 2. To get the type conditions for any STI models in the chain
|
30
|
+
def target_scope
|
31
|
+
scope = super
|
32
|
+
reflection.chain.drop(1).each do |reflection|
|
33
|
+
relation = reflection.klass.scope_for_association
|
34
|
+
scope.merge!(
|
35
|
+
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
scope
|
39
|
+
end
|
40
|
+
|
41
|
+
# Construct attributes for :through pointing to owner and associate. This is used by the
|
42
|
+
# methods which create and delete records on the association.
|
43
|
+
#
|
44
|
+
# We only support indirectly modifying through associations which have a belongs_to source.
|
45
|
+
# This is the "has_many :tags, through: :taggings" situation, where the join model
|
46
|
+
# typically has a belongs_to on both side. In other words, associations which could also
|
47
|
+
# be represented as has_and_belongs_to_many associations.
|
48
|
+
#
|
49
|
+
# We do not support creating/deleting records on the association where the source has
|
50
|
+
# some other type, because this opens up a whole can of worms, and in basically any
|
51
|
+
# situation it is more natural for the user to just create or modify their join records
|
52
|
+
# directly as required.
|
53
|
+
def construct_join_attributes(*records)
|
54
|
+
ensure_mutable
|
55
|
+
|
56
|
+
association_primary_key = source_reflection.association_primary_key(reflection.klass)
|
57
|
+
|
58
|
+
if association_primary_key == reflection.klass.primary_key && !options[:source_type]
|
59
|
+
join_attributes = { source_reflection.name => records }
|
60
|
+
else
|
61
|
+
join_attributes = {
|
62
|
+
source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
if options[:source_type]
|
67
|
+
join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
|
68
|
+
end
|
69
|
+
|
70
|
+
if records.count == 1
|
71
|
+
join_attributes.transform_values!(&:first)
|
72
|
+
else
|
73
|
+
join_attributes
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Note: this does not capture all cases, for example it would be crazy to try to
|
78
|
+
# properly support stale-checking for nested associations.
|
79
|
+
def stale_state
|
80
|
+
if through_reflection.belongs_to?
|
81
|
+
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def foreign_key_present?
|
86
|
+
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_mutable
|
90
|
+
unless source_reflection.belongs_to?
|
91
|
+
if reflection.has_one?
|
92
|
+
raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
93
|
+
else
|
94
|
+
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def ensure_not_nested
|
100
|
+
if reflection.nested?
|
101
|
+
if reflection.has_one?
|
102
|
+
raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
|
103
|
+
else
|
104
|
+
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_record(attributes)
|
110
|
+
inverse = source_reflection.inverse_of
|
111
|
+
target = through_association.target
|
112
|
+
|
113
|
+
if inverse && target && !target.is_a?(Array)
|
114
|
+
attributes[inverse.foreign_key] = target.id
|
115
|
+
end
|
116
|
+
|
117
|
+
super
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/forbidden_attributes_protection"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module AttributeAssignment
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveModel::AttributeAssignment
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def _assign_attributes(attributes)
|
13
|
+
multi_parameter_attributes = {}
|
14
|
+
nested_parameter_attributes = {}
|
15
|
+
|
16
|
+
attributes.each do |k, v|
|
17
|
+
if k.include?("(")
|
18
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
19
|
+
elsif v.is_a?(Hash)
|
20
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
super(attributes)
|
24
|
+
|
25
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
26
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
30
|
+
def assign_nested_parameter_attributes(pairs)
|
31
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
35
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
36
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
37
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
38
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
39
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
40
|
+
def assign_multiparameter_attributes(pairs)
|
41
|
+
execute_callstack_for_multiparameter_attributes(
|
42
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
47
|
+
errors = []
|
48
|
+
callstack.each do |name, values_with_empty_parameters|
|
49
|
+
begin
|
50
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
51
|
+
values = nil
|
52
|
+
else
|
53
|
+
values = values_with_empty_parameters
|
54
|
+
end
|
55
|
+
send("#{name}=", values)
|
56
|
+
rescue => ex
|
57
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
unless errors.empty?
|
61
|
+
error_descriptions = errors.map(&:message).join(",")
|
62
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
67
|
+
attributes = {}
|
68
|
+
|
69
|
+
pairs.each do |(multiparameter_name, value)|
|
70
|
+
attribute_name = multiparameter_name.split("(").first
|
71
|
+
attributes[attribute_name] ||= {}
|
72
|
+
|
73
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
74
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
75
|
+
end
|
76
|
+
|
77
|
+
attributes
|
78
|
+
end
|
79
|
+
|
80
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
81
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_parameter_position(multiparameter_name)
|
85
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeDecorators # :nodoc:
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal:
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods # :nodoc:
|
12
|
+
# This method is an internal API used to create class macros such as
|
13
|
+
# +serialize+, and features like time zone aware attributes.
|
14
|
+
#
|
15
|
+
# Used to wrap the type of an attribute in a new type.
|
16
|
+
# When the schema for a model is loaded, attributes with the same name as
|
17
|
+
# +column_name+ will have their type yielded to the given block. The
|
18
|
+
# return value of that block will be used instead.
|
19
|
+
#
|
20
|
+
# Subsequent calls where +column_name+ and +decorator_name+ are the same
|
21
|
+
# will override the previous decorator, not decorate twice. This can be
|
22
|
+
# used to create idempotent class macros like +serialize+
|
23
|
+
def decorate_attribute_type(column_name, decorator_name, &block)
|
24
|
+
matcher = ->(name, _) { name == column_name.to_s }
|
25
|
+
key = "_#{column_name}_#{decorator_name}"
|
26
|
+
decorate_matching_attribute_types(matcher, key, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# This method is an internal API used to create higher level features like
|
30
|
+
# time zone aware attributes.
|
31
|
+
#
|
32
|
+
# When the schema for a model is loaded, +matcher+ will be called for each
|
33
|
+
# attribute with its name and type. If the matcher returns a truthy value,
|
34
|
+
# the type will then be yielded to the given block, and the return value
|
35
|
+
# of that block will replace the type.
|
36
|
+
#
|
37
|
+
# Subsequent calls to this method with the same value for +decorator_name+
|
38
|
+
# will replace the previous decorator, not decorate twice. This can be
|
39
|
+
# used to ensure that class macros are idempotent.
|
40
|
+
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
41
|
+
reload_schema_from_cache
|
42
|
+
decorator_name = decorator_name.to_s
|
43
|
+
|
44
|
+
# Create new hashes so we don't modify parent classes
|
45
|
+
self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def load_schema!
|
51
|
+
super
|
52
|
+
attribute_types.each do |name, type|
|
53
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
54
|
+
define_attribute(name, decorated_type)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class TypeDecorator # :nodoc:
|
60
|
+
delegate :clear, to: :@decorations
|
61
|
+
|
62
|
+
def initialize(decorations = {})
|
63
|
+
@decorations = decorations
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge(*args)
|
67
|
+
TypeDecorator.new(@decorations.merge(*args))
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply(name, type)
|
71
|
+
decorations = decorators_for(name, type)
|
72
|
+
decorations.inject(type) do |new_type, block|
|
73
|
+
block.call(new_type)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def decorators_for(name, type)
|
80
|
+
matching(name, type).map(&:last)
|
81
|
+
end
|
82
|
+
|
83
|
+
def matching(name, type)
|
84
|
+
@decorations.values.select do |(matcher, _)|
|
85
|
+
matcher.call(name, type)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,492 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mutex_m"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
# = Active Record Attribute Methods
|
7
|
+
module AttributeMethods
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
|
11
|
+
included do
|
12
|
+
initialize_generated_modules
|
13
|
+
include Read
|
14
|
+
include Write
|
15
|
+
include BeforeTypeCast
|
16
|
+
include Query
|
17
|
+
include PrimaryKey
|
18
|
+
include TimeZoneConversion
|
19
|
+
include Dirty
|
20
|
+
include Serialization
|
21
|
+
|
22
|
+
delegate :column_for_attribute, to: :class
|
23
|
+
end
|
24
|
+
|
25
|
+
AttrNames = Module.new {
|
26
|
+
def self.set_name_cache(name, value)
|
27
|
+
const_name = "ATTR_#{name}"
|
28
|
+
unless const_defined? const_name
|
29
|
+
const_set const_name, value.dup.freeze
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
35
|
+
|
36
|
+
class GeneratedAttributeMethods < Module #:nodoc:
|
37
|
+
include Mutex_m
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def inherited(child_class) #:nodoc:
|
42
|
+
child_class.initialize_generated_modules
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_generated_modules # :nodoc:
|
47
|
+
@generated_attribute_methods = GeneratedAttributeMethods.new
|
48
|
+
@attribute_methods_generated = false
|
49
|
+
include @generated_attribute_methods
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generates all the attribute related methods for columns in the database
|
55
|
+
# accessors, mutators and query methods.
|
56
|
+
def define_attribute_methods # :nodoc:
|
57
|
+
return false if @attribute_methods_generated
|
58
|
+
# Use a mutex; we don't want two threads simultaneously trying to define
|
59
|
+
# attribute methods.
|
60
|
+
generated_attribute_methods.synchronize do
|
61
|
+
return false if @attribute_methods_generated
|
62
|
+
superclass.define_attribute_methods unless self == base_class
|
63
|
+
super(attribute_names)
|
64
|
+
@attribute_methods_generated = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def undefine_attribute_methods # :nodoc:
|
69
|
+
generated_attribute_methods.synchronize do
|
70
|
+
super if defined?(@attribute_methods_generated) && @attribute_methods_generated
|
71
|
+
@attribute_methods_generated = false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Raises an ActiveRecord::DangerousAttributeError exception when an
|
76
|
+
# \Active \Record method is defined in the model, otherwise +false+.
|
77
|
+
#
|
78
|
+
# class Person < ActiveRecord::Base
|
79
|
+
# def save
|
80
|
+
# 'already defined by Active Record'
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# Person.instance_method_already_implemented?(:save)
|
85
|
+
# # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
|
86
|
+
#
|
87
|
+
# Person.instance_method_already_implemented?(:name)
|
88
|
+
# # => false
|
89
|
+
def instance_method_already_implemented?(method_name)
|
90
|
+
if dangerous_attribute_method?(method_name)
|
91
|
+
raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
|
92
|
+
end
|
93
|
+
|
94
|
+
if superclass == Base
|
95
|
+
super
|
96
|
+
else
|
97
|
+
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
|
98
|
+
# defines its own attribute method, then we don't want to overwrite that.
|
99
|
+
defined = method_defined_within?(method_name, superclass, Base) &&
|
100
|
+
! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
|
101
|
+
defined || super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# A method name is 'dangerous' if it is already (re)defined by Active Record, but
|
106
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
107
|
+
def dangerous_attribute_method?(name) # :nodoc:
|
108
|
+
method_defined_within?(name, Base)
|
109
|
+
end
|
110
|
+
|
111
|
+
def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
112
|
+
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
113
|
+
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
|
114
|
+
klass.instance_method(name).owner != superklass.instance_method(name).owner
|
115
|
+
else
|
116
|
+
true
|
117
|
+
end
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
|
124
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
|
125
|
+
def dangerous_class_method?(method_name)
|
126
|
+
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
|
127
|
+
end
|
128
|
+
|
129
|
+
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
130
|
+
if klass.respond_to?(name, true)
|
131
|
+
if superklass.respond_to?(name, true)
|
132
|
+
klass.method(name).owner != superklass.method(name).owner
|
133
|
+
else
|
134
|
+
true
|
135
|
+
end
|
136
|
+
else
|
137
|
+
false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns +true+ if +attribute+ is an attribute method and table exists,
|
142
|
+
# +false+ otherwise.
|
143
|
+
#
|
144
|
+
# class Person < ActiveRecord::Base
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# Person.attribute_method?('name') # => true
|
148
|
+
# Person.attribute_method?(:age=) # => true
|
149
|
+
# Person.attribute_method?(:nothing) # => false
|
150
|
+
def attribute_method?(attribute)
|
151
|
+
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, "")))
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns an array of column names as strings if it's not an abstract class and
|
155
|
+
# table exists. Otherwise it returns an empty array.
|
156
|
+
#
|
157
|
+
# class Person < ActiveRecord::Base
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# Person.attribute_names
|
161
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
162
|
+
def attribute_names
|
163
|
+
@attribute_names ||= if !abstract_class? && table_exists?
|
164
|
+
attribute_types.keys
|
165
|
+
else
|
166
|
+
[]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Regexp whitelist. Matches the following:
|
171
|
+
# "#{table_name}.#{column_name}"
|
172
|
+
# "#{column_name}"
|
173
|
+
COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
|
174
|
+
|
175
|
+
# Regexp whitelist. Matches the following:
|
176
|
+
# "#{table_name}.#{column_name}"
|
177
|
+
# "#{table_name}.#{column_name} #{direction}"
|
178
|
+
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
179
|
+
# "#{table_name}.#{column_name} NULLS LAST"
|
180
|
+
# "#{column_name}"
|
181
|
+
# "#{column_name} #{direction}"
|
182
|
+
# "#{column_name} #{direction} NULLS FIRST"
|
183
|
+
# "#{column_name} NULLS LAST"
|
184
|
+
COLUMN_NAME_ORDER_WHITELIST = /
|
185
|
+
\A
|
186
|
+
(?:\w+\.)?
|
187
|
+
\w+
|
188
|
+
(?:\s+asc|\s+desc)?
|
189
|
+
(?:\s+nulls\s+(?:first|last))?
|
190
|
+
\z
|
191
|
+
/ix
|
192
|
+
|
193
|
+
def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
|
194
|
+
unexpected = args.reject do |arg|
|
195
|
+
arg.kind_of?(Arel::Node) ||
|
196
|
+
arg.is_a?(Arel::Nodes::SqlLiteral) ||
|
197
|
+
arg.is_a?(Arel::Attributes::Attribute) ||
|
198
|
+
arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
|
199
|
+
end
|
200
|
+
|
201
|
+
return if unexpected.none?
|
202
|
+
|
203
|
+
if allow_unsafe_raw_sql == :deprecated
|
204
|
+
ActiveSupport::Deprecation.warn(
|
205
|
+
"Dangerous query method (method whose arguments are used as raw " \
|
206
|
+
"SQL) called with non-attribute argument(s): " \
|
207
|
+
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
|
208
|
+
"arguments will be disallowed in Rails 6.0. This method should " \
|
209
|
+
"not be called with user-provided values, such as request " \
|
210
|
+
"parameters or model attributes. Known-safe values can be passed " \
|
211
|
+
"by wrapping them in Arel.sql()."
|
212
|
+
)
|
213
|
+
else
|
214
|
+
raise(ActiveRecord::UnknownAttributeReference,
|
215
|
+
"Query method called with non-attribute argument(s): " +
|
216
|
+
unexpected.map(&:inspect).join(", ")
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns true if the given attribute exists, otherwise false.
|
222
|
+
#
|
223
|
+
# class Person < ActiveRecord::Base
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# Person.has_attribute?('name') # => true
|
227
|
+
# Person.has_attribute?(:age) # => true
|
228
|
+
# Person.has_attribute?(:nothing) # => false
|
229
|
+
def has_attribute?(attr_name)
|
230
|
+
attribute_types.key?(attr_name.to_s)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Returns the column object for the named attribute.
|
234
|
+
# Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
|
235
|
+
# named attribute does not exist.
|
236
|
+
#
|
237
|
+
# class Person < ActiveRecord::Base
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# person = Person.new
|
241
|
+
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
|
242
|
+
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
|
243
|
+
#
|
244
|
+
# person.column_for_attribute(:nothing)
|
245
|
+
# # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
|
246
|
+
def column_for_attribute(name)
|
247
|
+
name = name.to_s
|
248
|
+
columns_hash.fetch(name) do
|
249
|
+
ConnectionAdapters::NullColumn.new(name)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
255
|
+
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
|
256
|
+
# which will all return +true+. It also defines the attribute methods if they have
|
257
|
+
# not been generated.
|
258
|
+
#
|
259
|
+
# class Person < ActiveRecord::Base
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# person = Person.new
|
263
|
+
# person.respond_to?(:name) # => true
|
264
|
+
# person.respond_to?(:name=) # => true
|
265
|
+
# person.respond_to?(:name?) # => true
|
266
|
+
# person.respond_to?('age') # => true
|
267
|
+
# person.respond_to?('age=') # => true
|
268
|
+
# person.respond_to?('age?') # => true
|
269
|
+
# person.respond_to?(:nothing) # => false
|
270
|
+
def respond_to?(name, include_private = false)
|
271
|
+
return false unless super
|
272
|
+
|
273
|
+
case name
|
274
|
+
when :to_partial_path
|
275
|
+
name = "to_partial_path".freeze
|
276
|
+
when :to_model
|
277
|
+
name = "to_model".freeze
|
278
|
+
else
|
279
|
+
name = name.to_s
|
280
|
+
end
|
281
|
+
|
282
|
+
# If the result is true then check for the select case.
|
283
|
+
# For queries selecting a subset of columns, return false for unselected columns.
|
284
|
+
# We check defined?(@attributes) not to issue warnings if called on objects that
|
285
|
+
# have been allocated but not yet initialized.
|
286
|
+
if defined?(@attributes) && self.class.column_names.include?(name)
|
287
|
+
return has_attribute?(name)
|
288
|
+
end
|
289
|
+
|
290
|
+
true
|
291
|
+
end
|
292
|
+
|
293
|
+
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
|
294
|
+
#
|
295
|
+
# class Person < ActiveRecord::Base
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
# person = Person.new
|
299
|
+
# person.has_attribute?(:name) # => true
|
300
|
+
# person.has_attribute?('age') # => true
|
301
|
+
# person.has_attribute?(:nothing) # => false
|
302
|
+
def has_attribute?(attr_name)
|
303
|
+
@attributes.key?(attr_name.to_s)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Returns an array of names for the attributes available on this object.
|
307
|
+
#
|
308
|
+
# class Person < ActiveRecord::Base
|
309
|
+
# end
|
310
|
+
#
|
311
|
+
# person = Person.new
|
312
|
+
# person.attribute_names
|
313
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
314
|
+
def attribute_names
|
315
|
+
@attributes.keys
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
319
|
+
#
|
320
|
+
# class Person < ActiveRecord::Base
|
321
|
+
# end
|
322
|
+
#
|
323
|
+
# person = Person.create(name: 'Francesco', age: 22)
|
324
|
+
# person.attributes
|
325
|
+
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
|
326
|
+
def attributes
|
327
|
+
@attributes.to_hash
|
328
|
+
end
|
329
|
+
|
330
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
331
|
+
# attribute +attr_name+. String attributes are truncated up to 50
|
332
|
+
# characters, Date and Time attributes are returned in the
|
333
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
334
|
+
# <tt>#inspect</tt> without modification.
|
335
|
+
#
|
336
|
+
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
|
337
|
+
#
|
338
|
+
# person.attribute_for_inspect(:name)
|
339
|
+
# # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
|
340
|
+
#
|
341
|
+
# person.attribute_for_inspect(:created_at)
|
342
|
+
# # => "\"2012-10-22 00:15:07\""
|
343
|
+
#
|
344
|
+
# person.attribute_for_inspect(:tag_ids)
|
345
|
+
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
|
346
|
+
def attribute_for_inspect(attr_name)
|
347
|
+
value = read_attribute(attr_name)
|
348
|
+
|
349
|
+
if value.is_a?(String) && value.length > 50
|
350
|
+
"#{value[0, 50]}...".inspect
|
351
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
352
|
+
%("#{value.to_s(:db)}")
|
353
|
+
else
|
354
|
+
value.inspect
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Returns +true+ if the specified +attribute+ has been set by the user or by a
|
359
|
+
# database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
|
360
|
+
# to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
|
361
|
+
# Note that it always returns +true+ with boolean attributes.
|
362
|
+
#
|
363
|
+
# class Task < ActiveRecord::Base
|
364
|
+
# end
|
365
|
+
#
|
366
|
+
# task = Task.new(title: '', is_done: false)
|
367
|
+
# task.attribute_present?(:title) # => false
|
368
|
+
# task.attribute_present?(:is_done) # => true
|
369
|
+
# task.title = 'Buy milk'
|
370
|
+
# task.is_done = true
|
371
|
+
# task.attribute_present?(:title) # => true
|
372
|
+
# task.attribute_present?(:is_done) # => true
|
373
|
+
def attribute_present?(attribute)
|
374
|
+
value = _read_attribute(attribute)
|
375
|
+
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
379
|
+
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
|
380
|
+
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
|
381
|
+
#
|
382
|
+
# Note: +:id+ is always present.
|
383
|
+
#
|
384
|
+
# class Person < ActiveRecord::Base
|
385
|
+
# belongs_to :organization
|
386
|
+
# end
|
387
|
+
#
|
388
|
+
# person = Person.new(name: 'Francesco', age: '22')
|
389
|
+
# person[:name] # => "Francesco"
|
390
|
+
# person[:age] # => 22
|
391
|
+
#
|
392
|
+
# person = Person.select('id').first
|
393
|
+
# person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
|
394
|
+
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
|
395
|
+
def [](attr_name)
|
396
|
+
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
397
|
+
end
|
398
|
+
|
399
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
400
|
+
# (Alias for the protected #write_attribute method).
|
401
|
+
#
|
402
|
+
# class Person < ActiveRecord::Base
|
403
|
+
# end
|
404
|
+
#
|
405
|
+
# person = Person.new
|
406
|
+
# person[:age] = '22'
|
407
|
+
# person[:age] # => 22
|
408
|
+
# person[:age].class # => Integer
|
409
|
+
def []=(attr_name, value)
|
410
|
+
write_attribute(attr_name, value)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns the name of all database fields which have been read from this
|
414
|
+
# model. This can be useful in development mode to determine which fields
|
415
|
+
# need to be selected. For performance critical pages, selecting only the
|
416
|
+
# required fields can be an easy performance win (assuming you aren't using
|
417
|
+
# all of the fields on the model).
|
418
|
+
#
|
419
|
+
# For example:
|
420
|
+
#
|
421
|
+
# class PostsController < ActionController::Base
|
422
|
+
# after_action :print_accessed_fields, only: :index
|
423
|
+
#
|
424
|
+
# def index
|
425
|
+
# @posts = Post.all
|
426
|
+
# end
|
427
|
+
#
|
428
|
+
# private
|
429
|
+
#
|
430
|
+
# def print_accessed_fields
|
431
|
+
# p @posts.first.accessed_fields
|
432
|
+
# end
|
433
|
+
# end
|
434
|
+
#
|
435
|
+
# Which allows you to quickly change your code to:
|
436
|
+
#
|
437
|
+
# class PostsController < ActionController::Base
|
438
|
+
# def index
|
439
|
+
# @posts = Post.select(:id, :title, :author_id, :updated_at)
|
440
|
+
# end
|
441
|
+
# end
|
442
|
+
def accessed_fields
|
443
|
+
@attributes.accessed
|
444
|
+
end
|
445
|
+
|
446
|
+
protected
|
447
|
+
|
448
|
+
def attribute_method?(attr_name) # :nodoc:
|
449
|
+
# We check defined? because Syck calls respond_to? before actually calling initialize.
|
450
|
+
defined?(@attributes) && @attributes.key?(attr_name)
|
451
|
+
end
|
452
|
+
|
453
|
+
private
|
454
|
+
|
455
|
+
def attributes_with_values_for_create(attribute_names)
|
456
|
+
attributes_with_values(attributes_for_create(attribute_names))
|
457
|
+
end
|
458
|
+
|
459
|
+
def attributes_with_values_for_update(attribute_names)
|
460
|
+
attributes_with_values(attributes_for_update(attribute_names))
|
461
|
+
end
|
462
|
+
|
463
|
+
def attributes_with_values(attribute_names)
|
464
|
+
attribute_names.each_with_object({}) do |name, attrs|
|
465
|
+
attrs[name] = _read_attribute(name)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Filters the primary keys and readonly attributes from the attribute names.
|
470
|
+
def attributes_for_update(attribute_names)
|
471
|
+
attribute_names.reject do |name|
|
472
|
+
readonly_attribute?(name)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Filters out the primary keys, from the attribute names, when the primary
|
477
|
+
# key is to be generated (e.g. the id attribute has no value).
|
478
|
+
def attributes_for_create(attribute_names)
|
479
|
+
attribute_names.reject do |name|
|
480
|
+
pk_attribute?(name) && id.nil?
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def readonly_attribute?(name)
|
485
|
+
self.class.readonly_attributes.include?(name)
|
486
|
+
end
|
487
|
+
|
488
|
+
def pk_attribute?(name)
|
489
|
+
name == self.class.primary_key
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|