activerecord 3.2.19 → 5.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 +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,12 +1,18 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Associations
|
5
3
|
# = Active Record Association Collection
|
6
4
|
#
|
7
5
|
# CollectionAssociation is an abstract class that provides common stuff to
|
8
6
|
# ease the implementation of association proxies that represent
|
9
|
-
# collections. See the class hierarchy in
|
7
|
+
# collections. See the class hierarchy in Association.
|
8
|
+
#
|
9
|
+
# CollectionAssociation:
|
10
|
+
# HasManyAssociation => has_many
|
11
|
+
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
12
|
+
#
|
13
|
+
# CollectionAssociation class provides common methods to the collections
|
14
|
+
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
15
|
+
# +:through association+ option.
|
10
16
|
#
|
11
17
|
# You need to be careful with assumptions regarding the target: The proxy
|
12
18
|
# does not fetch records from the database until it needs them, but new
|
@@ -18,22 +24,28 @@ module ActiveRecord
|
|
18
24
|
# If you need to work on all current children, new and existing records,
|
19
25
|
# +load_target+ and the +loaded+ flag are your friends.
|
20
26
|
class CollectionAssociation < Association #:nodoc:
|
21
|
-
attr_reader :proxy
|
22
|
-
|
23
|
-
def initialize(owner, reflection)
|
24
|
-
super
|
25
|
-
@proxy = CollectionProxy.new(self)
|
26
|
-
end
|
27
27
|
|
28
28
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
29
29
|
def reader(force_reload = false)
|
30
30
|
if force_reload
|
31
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
32
|
+
Passing an argument to force an association to reload is now
|
33
|
+
deprecated and will be removed in Rails 5.1. Please call `reload`
|
34
|
+
on the result collection proxy instead.
|
35
|
+
MSG
|
36
|
+
|
31
37
|
klass.uncached { reload }
|
32
38
|
elsif stale_target?
|
33
39
|
reload
|
34
40
|
end
|
35
41
|
|
36
|
-
|
42
|
+
if null_scope?
|
43
|
+
# Cache the proxy separately before the owner has an id
|
44
|
+
# or else a post-save proxy will still lack the id
|
45
|
+
@null_proxy ||= CollectionProxy.create(klass, self)
|
46
|
+
else
|
47
|
+
@proxy ||= CollectionProxy.create(klass, self)
|
48
|
+
end
|
37
49
|
end
|
38
50
|
|
39
51
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -43,45 +55,39 @@ module ActiveRecord
|
|
43
55
|
|
44
56
|
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
45
57
|
def ids_reader
|
46
|
-
if
|
58
|
+
if loaded?
|
47
59
|
load_target.map do |record|
|
48
60
|
record.send(reflection.association_primary_key)
|
49
61
|
end
|
50
62
|
else
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if including.any?
|
57
|
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
|
58
|
-
relation = join_dependency.join_associations.inject(relation) do |r, association|
|
59
|
-
association.join_relation(r)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
relation.pluck(column)
|
63
|
+
@association_ids ||= (
|
64
|
+
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
|
65
|
+
scope.pluck(column)
|
66
|
+
)
|
64
67
|
end
|
65
68
|
end
|
66
69
|
|
67
70
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
68
71
|
def ids_writer(ids)
|
69
|
-
|
70
|
-
ids = Array
|
71
|
-
ids.map! { |i|
|
72
|
-
|
72
|
+
pk_type = reflection.primary_key_type
|
73
|
+
ids = Array(ids).reject(&:blank?)
|
74
|
+
ids.map! { |i| pk_type.cast(i) }
|
75
|
+
records = klass.where(reflection.association_primary_key => ids).index_by do |r|
|
76
|
+
r.send(reflection.association_primary_key)
|
77
|
+
end.values_at(*ids)
|
78
|
+
replace(records)
|
73
79
|
end
|
74
80
|
|
75
81
|
def reset
|
76
|
-
|
82
|
+
super
|
77
83
|
@target = []
|
78
84
|
end
|
79
85
|
|
80
|
-
def select(
|
86
|
+
def select(*fields)
|
81
87
|
if block_given?
|
82
88
|
load_target.select.each { |e| yield e }
|
83
89
|
else
|
84
|
-
|
90
|
+
scope.select(*fields)
|
85
91
|
end
|
86
92
|
end
|
87
93
|
|
@@ -89,46 +95,94 @@ module ActiveRecord
|
|
89
95
|
if block_given?
|
90
96
|
load_target.find(*args) { |*block_args| yield(*block_args) }
|
91
97
|
else
|
92
|
-
if options[:
|
93
|
-
|
98
|
+
if options[:inverse_of] && loaded?
|
99
|
+
args_flatten = args.flatten
|
100
|
+
raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
|
101
|
+
result = find_by_scan(*args)
|
102
|
+
|
103
|
+
result_size = Array(result).size
|
104
|
+
if !result || result_size != args_flatten.size
|
105
|
+
scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
|
106
|
+
else
|
107
|
+
result
|
108
|
+
end
|
94
109
|
else
|
95
|
-
|
110
|
+
scope.find(*args)
|
96
111
|
end
|
97
112
|
end
|
98
113
|
end
|
99
114
|
|
100
115
|
def first(*args)
|
101
|
-
|
116
|
+
first_nth_or_last(:first, *args)
|
117
|
+
end
|
118
|
+
|
119
|
+
def second(*args)
|
120
|
+
first_nth_or_last(:second, *args)
|
121
|
+
end
|
122
|
+
|
123
|
+
def third(*args)
|
124
|
+
first_nth_or_last(:third, *args)
|
125
|
+
end
|
126
|
+
|
127
|
+
def fourth(*args)
|
128
|
+
first_nth_or_last(:fourth, *args)
|
129
|
+
end
|
130
|
+
|
131
|
+
def fifth(*args)
|
132
|
+
first_nth_or_last(:fifth, *args)
|
133
|
+
end
|
134
|
+
|
135
|
+
def forty_two(*args)
|
136
|
+
first_nth_or_last(:forty_two, *args)
|
137
|
+
end
|
138
|
+
|
139
|
+
def third_to_last(*args)
|
140
|
+
first_nth_or_last(:third_to_last, *args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def second_to_last(*args)
|
144
|
+
first_nth_or_last(:second_to_last, *args)
|
102
145
|
end
|
103
146
|
|
104
147
|
def last(*args)
|
105
|
-
|
148
|
+
first_nth_or_last(:last, *args)
|
106
149
|
end
|
107
150
|
|
108
|
-
def
|
151
|
+
def take(n = nil)
|
152
|
+
if loaded?
|
153
|
+
n ? target.take(n) : target.first
|
154
|
+
else
|
155
|
+
scope.take(n).tap do |record|
|
156
|
+
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def build(attributes = {}, &block)
|
109
162
|
if attributes.is_a?(Array)
|
110
|
-
attributes.collect { |attr| build(attr,
|
163
|
+
attributes.collect { |attr| build(attr, &block) }
|
111
164
|
else
|
112
|
-
add_to_target(build_record(attributes
|
165
|
+
add_to_target(build_record(attributes)) do |record|
|
113
166
|
yield(record) if block_given?
|
114
167
|
end
|
115
168
|
end
|
116
169
|
end
|
117
170
|
|
118
|
-
def create(attributes = {},
|
119
|
-
|
171
|
+
def create(attributes = {}, &block)
|
172
|
+
_create_record(attributes, &block)
|
120
173
|
end
|
121
174
|
|
122
|
-
def create!(attributes = {},
|
123
|
-
|
175
|
+
def create!(attributes = {}, &block)
|
176
|
+
_create_record(attributes, true, &block)
|
124
177
|
end
|
125
178
|
|
126
|
-
# Add +records+ to this association. Returns +self+ so method calls may
|
127
|
-
# Since << flattens its argument list and inserts each record,
|
179
|
+
# Add +records+ to this association. Returns +self+ so method calls may
|
180
|
+
# be chained. Since << flattens its argument list and inserts each record,
|
181
|
+
# +push+ and +concat+ behave identically.
|
128
182
|
def concat(*records)
|
129
|
-
|
130
|
-
|
183
|
+
records = records.flatten
|
131
184
|
if owner.new_record?
|
185
|
+
load_target
|
132
186
|
concat_records(records)
|
133
187
|
else
|
134
188
|
transaction { concat_records(records) }
|
@@ -150,23 +204,38 @@ module ActiveRecord
|
|
150
204
|
end
|
151
205
|
end
|
152
206
|
|
153
|
-
#
|
207
|
+
# Removes all records from the association without calling callbacks
|
208
|
+
# on the associated records. It honors the +:dependent+ option. However
|
209
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
210
|
+
# deletion strategy for the association is applied.
|
211
|
+
#
|
212
|
+
# You can force a particular deletion strategy by passing a parameter.
|
213
|
+
#
|
214
|
+
# Example:
|
215
|
+
#
|
216
|
+
# @author.books.delete_all(:nullify)
|
217
|
+
# @author.books.delete_all(:delete_all)
|
154
218
|
#
|
155
219
|
# See delete for more info.
|
156
|
-
def delete_all
|
157
|
-
|
220
|
+
def delete_all(dependent = nil)
|
221
|
+
if dependent && ![:nullify, :delete_all].include?(dependent)
|
222
|
+
raise ArgumentError, "Valid values are :nullify or :delete_all"
|
223
|
+
end
|
224
|
+
|
225
|
+
dependent = if dependent
|
226
|
+
dependent
|
227
|
+
elsif options[:dependent] == :destroy
|
228
|
+
:delete_all
|
229
|
+
else
|
230
|
+
options[:dependent]
|
231
|
+
end
|
232
|
+
|
233
|
+
delete_or_nullify_all_records(dependent).tap do
|
158
234
|
reset
|
159
235
|
loaded!
|
160
236
|
end
|
161
237
|
end
|
162
238
|
|
163
|
-
# Called when the association is declared as :dependent => :delete_all. This is
|
164
|
-
# an optimised version which avoids loading the records into memory. Not really
|
165
|
-
# for public consumption.
|
166
|
-
def delete_all_on_destroy
|
167
|
-
scoped.delete_all
|
168
|
-
end
|
169
|
-
|
170
239
|
# Destroy all the records from this association.
|
171
240
|
#
|
172
241
|
# See destroy for more info.
|
@@ -177,46 +246,25 @@ module ActiveRecord
|
|
177
246
|
end
|
178
247
|
end
|
179
248
|
|
180
|
-
#
|
181
|
-
def sum(*args)
|
182
|
-
if block_given?
|
183
|
-
scoped.sum(*args) { |*block_args| yield(*block_args) }
|
184
|
-
else
|
185
|
-
scoped.sum(*args)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
|
190
|
-
# association, it will be used for the query. Otherwise, construct options and pass them with
|
249
|
+
# Count all records using SQL. Construct options and pass them with
|
191
250
|
# scope to the target class's +count+.
|
192
|
-
def count(column_name = nil
|
193
|
-
|
251
|
+
def count(column_name = nil)
|
252
|
+
relation = scope
|
253
|
+
if association_scope.distinct_value
|
254
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
255
|
+
column_name ||= reflection.klass.primary_key
|
256
|
+
relation = relation.distinct
|
257
|
+
end
|
194
258
|
|
195
|
-
|
259
|
+
value = relation.count(column_name)
|
196
260
|
|
197
|
-
|
198
|
-
|
199
|
-
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
|
200
|
-
end
|
261
|
+
limit = options[:limit]
|
262
|
+
offset = options[:offset]
|
201
263
|
|
202
|
-
|
264
|
+
if limit || offset
|
265
|
+
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
203
266
|
else
|
204
|
-
|
205
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
206
|
-
column_name ||= reflection.klass.primary_key
|
207
|
-
count_options.merge!(:distinct => true)
|
208
|
-
end
|
209
|
-
|
210
|
-
value = scoped.count(column_name, count_options)
|
211
|
-
|
212
|
-
limit = options[:limit]
|
213
|
-
offset = options[:offset]
|
214
|
-
|
215
|
-
if limit || offset
|
216
|
-
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
217
|
-
else
|
218
|
-
value
|
219
|
-
end
|
267
|
+
value
|
220
268
|
end
|
221
269
|
end
|
222
270
|
|
@@ -228,16 +276,22 @@ module ActiveRecord
|
|
228
276
|
# are actually removed from the database, that depends precisely on
|
229
277
|
# +delete_records+. They are in any case removed from the collection.
|
230
278
|
def delete(*records)
|
231
|
-
|
279
|
+
return if records.empty?
|
280
|
+
_options = records.extract_options!
|
281
|
+
dependent = _options[:dependent] || options[:dependent]
|
282
|
+
|
283
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
284
|
+
delete_or_destroy(records, dependent)
|
232
285
|
end
|
233
286
|
|
234
|
-
#
|
235
|
-
# +before_remove+
|
287
|
+
# Deletes the +records+ and removes them from this association calling
|
288
|
+
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
236
289
|
#
|
237
|
-
# Note that this method
|
238
|
-
#
|
290
|
+
# Note that this method removes records from the database ignoring the
|
291
|
+
# +:dependent+ option.
|
239
292
|
def destroy(*records)
|
240
|
-
|
293
|
+
return if records.empty?
|
294
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
241
295
|
delete_or_destroy(records, :destroy)
|
242
296
|
end
|
243
297
|
|
@@ -252,12 +306,16 @@ module ActiveRecord
|
|
252
306
|
# This method is abstract in the sense that it relies on
|
253
307
|
# +count_records+, which is a method descendants have to provide.
|
254
308
|
def size
|
255
|
-
if !find_target? ||
|
256
|
-
|
257
|
-
|
309
|
+
if !find_target? || loaded?
|
310
|
+
if association_scope.distinct_value
|
311
|
+
target.uniq.size
|
312
|
+
else
|
313
|
+
target.size
|
314
|
+
end
|
315
|
+
elsif !loaded? && !association_scope.group_values.empty?
|
258
316
|
load_target.size
|
259
|
-
elsif !loaded? && !
|
260
|
-
unsaved_records = target.select
|
317
|
+
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
|
318
|
+
unsaved_records = target.select(&:new_record?)
|
261
319
|
unsaved_records.size + count_records
|
262
320
|
else
|
263
321
|
count_records
|
@@ -273,13 +331,25 @@ module ActiveRecord
|
|
273
331
|
load_target.size
|
274
332
|
end
|
275
333
|
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
334
|
+
# Returns true if the collection is empty.
|
335
|
+
#
|
336
|
+
# If the collection has been loaded
|
337
|
+
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
338
|
+
# collection has not been loaded, it is equivalent to
|
339
|
+
# <tt>collection.exists?</tt>. If the collection has not already been
|
340
|
+
# loaded and you are going to fetch the records anyway it is better to
|
341
|
+
# check <tt>collection.length.zero?</tt>.
|
279
342
|
def empty?
|
280
|
-
|
343
|
+
if loaded?
|
344
|
+
size.zero?
|
345
|
+
else
|
346
|
+
@target.blank? && !scope.exists?
|
347
|
+
end
|
281
348
|
end
|
282
349
|
|
350
|
+
# Returns true if the collections is not empty.
|
351
|
+
# If block given, loads all records and checks for one or more matches.
|
352
|
+
# Otherwise, equivalent to +!collection.empty?+.
|
283
353
|
def any?
|
284
354
|
if block_given?
|
285
355
|
load_target.any? { |*block_args| yield(*block_args) }
|
@@ -288,7 +358,9 @@ module ActiveRecord
|
|
288
358
|
end
|
289
359
|
end
|
290
360
|
|
291
|
-
# Returns true if the collection has more than 1 record.
|
361
|
+
# Returns true if the collection has more than 1 record.
|
362
|
+
# If block given, loads all records and checks for two or more matches.
|
363
|
+
# Otherwise, equivalent to +collection.size > 1+.
|
292
364
|
def many?
|
293
365
|
if block_given?
|
294
366
|
load_target.many? { |*block_args| yield(*block_args) }
|
@@ -297,23 +369,29 @@ module ActiveRecord
|
|
297
369
|
end
|
298
370
|
end
|
299
371
|
|
300
|
-
def
|
372
|
+
def distinct
|
301
373
|
seen = {}
|
302
|
-
|
374
|
+
load_target.find_all do |record|
|
303
375
|
seen[record.id] = true unless seen.key?(record.id)
|
304
376
|
end
|
305
377
|
end
|
378
|
+
alias uniq distinct
|
306
379
|
|
307
|
-
# Replace this collection with +other_array
|
308
|
-
#
|
380
|
+
# Replace this collection with +other_array+. This will perform a diff
|
381
|
+
# and delete/add only records that have changed.
|
309
382
|
def replace(other_array)
|
310
|
-
other_array.each { |val| raise_on_type_mismatch(val) }
|
383
|
+
other_array.each { |val| raise_on_type_mismatch!(val) }
|
311
384
|
original_target = load_target.dup
|
312
385
|
|
313
386
|
if owner.new_record?
|
314
387
|
replace_records(other_array, original_target)
|
315
388
|
else
|
316
|
-
|
389
|
+
replace_common_records_in_memory(other_array, original_target)
|
390
|
+
if other_array != original_target
|
391
|
+
transaction { replace_records(other_array, original_target) }
|
392
|
+
else
|
393
|
+
other_array
|
394
|
+
end
|
317
395
|
end
|
318
396
|
end
|
319
397
|
|
@@ -322,8 +400,7 @@ module ActiveRecord
|
|
322
400
|
if record.new_record?
|
323
401
|
include_in_memory?(record)
|
324
402
|
else
|
325
|
-
|
326
|
-
loaded? ? target.include?(record) : scoped.exists?(record)
|
403
|
+
loaded? ? target.include?(record) : scope.exists?(record.id)
|
327
404
|
end
|
328
405
|
else
|
329
406
|
false
|
@@ -339,50 +416,61 @@ module ActiveRecord
|
|
339
416
|
target
|
340
417
|
end
|
341
418
|
|
342
|
-
def add_to_target(record)
|
343
|
-
|
419
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
420
|
+
if association_scope.distinct_value
|
421
|
+
index = @target.index(record)
|
422
|
+
end
|
423
|
+
replace_on_target(record, index, skip_callbacks, &block)
|
424
|
+
end
|
425
|
+
|
426
|
+
def replace_on_target(record, index, skip_callbacks)
|
427
|
+
callback(:before_add, record) unless skip_callbacks
|
428
|
+
|
429
|
+
was_loaded = loaded?
|
344
430
|
yield(record) if block_given?
|
345
431
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
432
|
+
unless !was_loaded && loaded?
|
433
|
+
if index
|
434
|
+
@target[index] = record
|
435
|
+
else
|
436
|
+
@target << record
|
437
|
+
end
|
350
438
|
end
|
351
439
|
|
352
|
-
callback(:after_add, record)
|
440
|
+
callback(:after_add, record) unless skip_callbacks
|
353
441
|
set_inverse_instance(record)
|
354
442
|
|
355
443
|
record
|
356
444
|
end
|
357
445
|
|
446
|
+
def scope(opts = {})
|
447
|
+
scope = super()
|
448
|
+
scope.none! if opts.fetch(:nullify, true) && null_scope?
|
449
|
+
scope
|
450
|
+
end
|
451
|
+
|
452
|
+
def null_scope?
|
453
|
+
owner.new_record? && !foreign_key_present?
|
454
|
+
end
|
455
|
+
|
358
456
|
private
|
457
|
+
def get_records
|
458
|
+
return scope.to_a if skip_statement_cache?
|
359
459
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
count_with = $2.to_s
|
367
|
-
count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
|
368
|
-
"SELECT #{$1}COUNT(#{count_with}) FROM"
|
369
|
-
end
|
370
|
-
end
|
460
|
+
conn = klass.connection
|
461
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
462
|
+
StatementCache.create(conn) { |params|
|
463
|
+
as = AssociationScope.create { params.bind }
|
464
|
+
target_scope.merge as.scope(self, conn)
|
465
|
+
}
|
371
466
|
end
|
372
467
|
|
373
|
-
|
374
|
-
|
375
|
-
|
468
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
469
|
+
sc.execute binds, klass, klass.connection
|
470
|
+
end
|
376
471
|
|
377
472
|
def find_target
|
378
|
-
records =
|
379
|
-
if options[:finder_sql]
|
380
|
-
reflection.klass.find_by_sql(custom_finder_sql)
|
381
|
-
else
|
382
|
-
scoped.all
|
383
|
-
end
|
384
|
-
|
385
|
-
records = options[:uniq] ? uniq(records) : records
|
473
|
+
records = get_records
|
386
474
|
records.each { |record| set_inverse_instance(record) }
|
387
475
|
records
|
388
476
|
end
|
@@ -402,12 +490,7 @@ module ActiveRecord
|
|
402
490
|
return memory if persisted.empty?
|
403
491
|
|
404
492
|
persisted.map! do |record|
|
405
|
-
|
406
|
-
# record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
|
407
|
-
mem_index = memory.index(record)
|
408
|
-
|
409
|
-
if mem_index
|
410
|
-
mem_record = memory.delete_at(mem_index)
|
493
|
+
if mem_record = memory.delete(record)
|
411
494
|
|
412
495
|
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
|
413
496
|
mem_record[name] = record[name]
|
@@ -422,16 +505,16 @@ module ActiveRecord
|
|
422
505
|
persisted + memory
|
423
506
|
end
|
424
507
|
|
425
|
-
def
|
508
|
+
def _create_record(attributes, raise = false, &block)
|
426
509
|
unless owner.persisted?
|
427
510
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
428
511
|
end
|
429
512
|
|
430
513
|
if attributes.is_a?(Array)
|
431
|
-
attributes.collect { |attr|
|
514
|
+
attributes.collect { |attr| _create_record(attr, raise, &block) }
|
432
515
|
else
|
433
516
|
transaction do
|
434
|
-
add_to_target(build_record(attributes
|
517
|
+
add_to_target(build_record(attributes)) do |record|
|
435
518
|
yield(record) if block_given?
|
436
519
|
insert_record(record, true, raise)
|
437
520
|
end
|
@@ -445,13 +528,13 @@ module ActiveRecord
|
|
445
528
|
end
|
446
529
|
|
447
530
|
def create_scope
|
448
|
-
|
531
|
+
scope.scope_for_create.stringify_keys
|
449
532
|
end
|
450
533
|
|
451
534
|
def delete_or_destroy(records, method)
|
452
535
|
records = records.flatten
|
453
|
-
records.each { |record| raise_on_type_mismatch(record) }
|
454
|
-
existing_records = records.reject
|
536
|
+
records.each { |record| raise_on_type_mismatch!(record) }
|
537
|
+
existing_records = records.reject(&:new_record?)
|
455
538
|
|
456
539
|
if existing_records.empty?
|
457
540
|
remove_records(existing_records, records, method)
|
@@ -487,13 +570,21 @@ module ActiveRecord
|
|
487
570
|
target
|
488
571
|
end
|
489
572
|
|
490
|
-
def
|
573
|
+
def replace_common_records_in_memory(new_target, original_target)
|
574
|
+
common_records = new_target & original_target
|
575
|
+
common_records.each do |record|
|
576
|
+
skip_callbacks = true
|
577
|
+
replace_on_target(record, @target.index(record), skip_callbacks)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def concat_records(records, should_raise = false)
|
491
582
|
result = true
|
492
583
|
|
493
|
-
records.
|
494
|
-
raise_on_type_mismatch(record)
|
495
|
-
add_to_target(record) do |
|
496
|
-
result &&= insert_record(
|
584
|
+
records.each do |record|
|
585
|
+
raise_on_type_mismatch!(record)
|
586
|
+
add_to_target(record) do |rec|
|
587
|
+
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
|
497
588
|
end
|
498
589
|
end
|
499
590
|
|
@@ -502,20 +593,13 @@ module ActiveRecord
|
|
502
593
|
|
503
594
|
def callback(method, record)
|
504
595
|
callbacks_for(method).each do |callback|
|
505
|
-
|
506
|
-
when Symbol
|
507
|
-
owner.send(callback, record)
|
508
|
-
when Proc
|
509
|
-
callback.call(owner, record)
|
510
|
-
else
|
511
|
-
callback.send(method, owner, record)
|
512
|
-
end
|
596
|
+
callback.call(method, owner, record)
|
513
597
|
end
|
514
598
|
end
|
515
599
|
|
516
600
|
def callbacks_for(callback_name)
|
517
601
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
518
|
-
owner.class.send(full_callback_name
|
602
|
+
owner.class.send(full_callback_name)
|
519
603
|
end
|
520
604
|
|
521
605
|
# Should we deal with assoc.first or assoc.last by issuing an independent query to
|
@@ -526,51 +610,49 @@ module ActiveRecord
|
|
526
610
|
# Otherwise, go to the database only if none of the following are true:
|
527
611
|
# * target already loaded
|
528
612
|
# * owner is new record
|
529
|
-
# * custom :finder_sql exists
|
530
613
|
# * target contains new or changed record(s)
|
531
|
-
|
532
|
-
def fetch_first_or_last_using_find?(args)
|
614
|
+
def fetch_first_nth_or_last_using_find?(args)
|
533
615
|
if args.first.is_a?(Hash)
|
534
616
|
true
|
535
617
|
else
|
536
618
|
!(loaded? ||
|
537
619
|
owner.new_record? ||
|
538
|
-
|
539
|
-
target.any? { |record| record.new_record? || record.changed? } ||
|
540
|
-
args.first.kind_of?(Integer))
|
620
|
+
target.any? { |record| record.new_record? || record.changed? })
|
541
621
|
end
|
542
622
|
end
|
543
623
|
|
544
624
|
def include_in_memory?(record)
|
545
625
|
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
546
|
-
owner.
|
547
|
-
|
548
|
-
|
626
|
+
assoc = owner.association(reflection.through_reflection.name)
|
627
|
+
assoc.reader.any? { |source|
|
628
|
+
target_reflection = source.send(reflection.source_reflection.name)
|
629
|
+
target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
|
549
630
|
} || target.include?(record)
|
550
631
|
else
|
551
632
|
target.include?(record)
|
552
633
|
end
|
553
634
|
end
|
554
635
|
|
555
|
-
# If
|
636
|
+
# If the :inverse_of option has been
|
637
|
+
# specified, then #find scans the entire collection.
|
556
638
|
def find_by_scan(*args)
|
557
639
|
expects_array = args.first.kind_of?(Array)
|
558
|
-
ids = args.flatten.compact.
|
640
|
+
ids = args.flatten.compact.map(&:to_s).uniq
|
559
641
|
|
560
642
|
if ids.size == 1
|
561
643
|
id = ids.first
|
562
|
-
record = load_target.detect { |r| id == r.id }
|
644
|
+
record = load_target.detect { |r| id == r.id.to_s }
|
563
645
|
expects_array ? [ record ] : record
|
564
646
|
else
|
565
|
-
load_target.select { |r| ids.include?(r.id) }
|
647
|
+
load_target.select { |r| ids.include?(r.id.to_s) }
|
566
648
|
end
|
567
649
|
end
|
568
650
|
|
569
651
|
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
570
|
-
def
|
652
|
+
def first_nth_or_last(type, *args)
|
571
653
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
572
654
|
|
573
|
-
collection =
|
655
|
+
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
|
574
656
|
collection.send(type, *args).tap do |record|
|
575
657
|
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
576
658
|
end
|