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.

Files changed (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,98 @@
1
+ module ActiveRecord
2
+ module ConnectionHandling
3
+ # Establishes the connection to the database. Accepts a hash as input where
4
+ # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
5
+ # example for regular databases (MySQL, Postgresql, etc):
6
+ #
7
+ # ActiveRecord::Base.establish_connection(
8
+ # adapter: "mysql",
9
+ # host: "localhost",
10
+ # username: "myuser",
11
+ # password: "mypass",
12
+ # database: "somedatabase"
13
+ # )
14
+ #
15
+ # Example for SQLite database:
16
+ #
17
+ # ActiveRecord::Base.establish_connection(
18
+ # adapter: "sqlite",
19
+ # database: "path/to/dbfile"
20
+ # )
21
+ #
22
+ # Also accepts keys as strings (for parsing from YAML for example):
23
+ #
24
+ # ActiveRecord::Base.establish_connection(
25
+ # "adapter" => "sqlite",
26
+ # "database" => "path/to/dbfile"
27
+ # )
28
+ #
29
+ # Or a URL:
30
+ #
31
+ # ActiveRecord::Base.establish_connection(
32
+ # "postgres://myuser:mypass@localhost/somedatabase"
33
+ # )
34
+ #
35
+ # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
36
+ # may be returned on an error.
37
+ def establish_connection(spec = ENV["DATABASE_URL"])
38
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
39
+ spec = resolver.spec
40
+
41
+ unless respond_to?(spec.adapter_method)
42
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
43
+ end
44
+
45
+ remove_connection
46
+ connection_handler.establish_connection self, spec
47
+ end
48
+
49
+ # Returns the connection currently associated with the class. This can
50
+ # also be used to "borrow" the connection to do database work unrelated
51
+ # to any of the specific Active Records.
52
+ def connection
53
+ retrieve_connection
54
+ end
55
+
56
+ def connection_id
57
+ ActiveRecord::RuntimeRegistry.connection_id
58
+ end
59
+
60
+ def connection_id=(connection_id)
61
+ ActiveRecord::RuntimeRegistry.connection_id = connection_id
62
+ end
63
+
64
+ # Returns the configuration of the associated connection as a hash:
65
+ #
66
+ # ActiveRecord::Base.connection_config
67
+ # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
68
+ #
69
+ # Please use only for reading.
70
+ def connection_config
71
+ connection_pool.spec.config
72
+ end
73
+
74
+ def connection_pool
75
+ connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
76
+ end
77
+
78
+ def retrieve_connection
79
+ connection_handler.retrieve_connection(self)
80
+ end
81
+
82
+ # Returns +true+ if Active Record is connected.
83
+ def connected?
84
+ connection_handler.connected?(self)
85
+ end
86
+
87
+ def remove_connection(klass = self)
88
+ connection_handler.remove_connection(klass)
89
+ end
90
+
91
+ def clear_cache! # :nodoc:
92
+ connection.schema_cache.clear!
93
+ end
94
+
95
+ delegate :clear_active_connections!, :clear_reloadable_connections!,
96
+ :clear_all_connections!, :to => :connection_handler
97
+ end
98
+ end
@@ -0,0 +1,463 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'active_support/core_ext/object/duplicable'
3
+ require 'thread'
4
+
5
+ module ActiveRecord
6
+ module Core
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ ##
11
+ # :singleton-method:
12
+ #
13
+ # Accepts a logger conforming to the interface of Log4r which is then
14
+ # passed on to any new database connections made and which can be
15
+ # retrieved on both a class and instance level by calling +logger+.
16
+ mattr_accessor :logger, instance_writer: false
17
+
18
+ ##
19
+ # :singleton-method:
20
+ # Contains the database configuration - as is typically stored in config/database.yml -
21
+ # as a Hash.
22
+ #
23
+ # For example, the following database.yml...
24
+ #
25
+ # development:
26
+ # adapter: sqlite3
27
+ # database: db/development.sqlite3
28
+ #
29
+ # production:
30
+ # adapter: sqlite3
31
+ # database: db/production.sqlite3
32
+ #
33
+ # ...would result in ActiveRecord::Base.configurations to look like this:
34
+ #
35
+ # {
36
+ # 'development' => {
37
+ # 'adapter' => 'sqlite3',
38
+ # 'database' => 'db/development.sqlite3'
39
+ # },
40
+ # 'production' => {
41
+ # 'adapter' => 'sqlite3',
42
+ # 'database' => 'db/production.sqlite3'
43
+ # }
44
+ # }
45
+ mattr_accessor :configurations, instance_writer: false
46
+ self.configurations = {}
47
+
48
+ ##
49
+ # :singleton-method:
50
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
51
+ # dates and times from the database. This is set to :utc by default.
52
+ mattr_accessor :default_timezone, instance_writer: false
53
+ self.default_timezone = :utc
54
+
55
+ ##
56
+ # :singleton-method:
57
+ # Specifies the format to use when dumping the database schema with Rails'
58
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
59
+ # specific) SQL statements. If :ruby, the schema is dumped as an
60
+ # ActiveRecord::Schema file which can be loaded into any database that
61
+ # supports migrations. Use :ruby if you want to have different database
62
+ # adapters for, e.g., your development and test environments.
63
+ mattr_accessor :schema_format, instance_writer: false
64
+ self.schema_format = :ruby
65
+
66
+ ##
67
+ # :singleton-method:
68
+ # Specify whether or not to use timestamps for migration versions
69
+ mattr_accessor :timestamped_migrations, instance_writer: false
70
+ self.timestamped_migrations = true
71
+
72
+ ##
73
+ # :singleton-method:
74
+ # Disable implicit join references. This feature was deprecated with Rails 4.
75
+ # If you don't make use of implicit references but still see deprecation warnings
76
+ # you can disable the feature entirely. This will be the default with Rails 4.1.
77
+ mattr_accessor :disable_implicit_join_references, instance_writer: false
78
+ self.disable_implicit_join_references = false
79
+
80
+ class_attribute :default_connection_handler, instance_writer: false
81
+
82
+ def self.connection_handler
83
+ ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
84
+ end
85
+
86
+ def self.connection_handler=(handler)
87
+ ActiveRecord::RuntimeRegistry.connection_handler = handler
88
+ end
89
+
90
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
91
+ end
92
+
93
+ module ClassMethods
94
+ def inherited(child_class) #:nodoc:
95
+ child_class.initialize_generated_modules
96
+ super
97
+ end
98
+
99
+ def initialize_generated_modules
100
+ @attribute_methods_mutex = Mutex.new
101
+
102
+ # force attribute methods to be higher in inheritance hierarchy than other generated methods
103
+ generated_attribute_methods.const_set(:AttrNames, Module.new {
104
+ def self.const_missing(name)
105
+ const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze)
106
+ end
107
+ })
108
+
109
+ generated_feature_methods
110
+ end
111
+
112
+ def generated_feature_methods
113
+ @generated_feature_methods ||= begin
114
+ mod = const_set(:GeneratedFeatureMethods, Module.new)
115
+ include mod
116
+ mod
117
+ end
118
+ end
119
+
120
+ # Returns a string like 'Post(id:integer, title:string, body:text)'
121
+ def inspect
122
+ if self == Base
123
+ super
124
+ elsif abstract_class?
125
+ "#{super}(abstract)"
126
+ elsif table_exists?
127
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
128
+ "#{super}(#{attr_list})"
129
+ else
130
+ "#{super}(Table doesn't exist)"
131
+ end
132
+ end
133
+
134
+ # Overwrite the default class equality method to provide support for association proxies.
135
+ def ===(object)
136
+ object.is_a?(self)
137
+ end
138
+
139
+ # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
140
+ #
141
+ # class Post < ActiveRecord::Base
142
+ # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
143
+ # end
144
+ def arel_table
145
+ @arel_table ||= Arel::Table.new(table_name, arel_engine)
146
+ end
147
+
148
+ # Returns the Arel engine.
149
+ def arel_engine
150
+ @arel_engine ||= begin
151
+ if Base == self || connection_handler.retrieve_connection_pool(self)
152
+ self
153
+ else
154
+ superclass.arel_engine
155
+ end
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def relation #:nodoc:
162
+ relation = Relation.new(self, arel_table)
163
+
164
+ if finder_needs_type_condition?
165
+ relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
166
+ else
167
+ relation
168
+ end
169
+ end
170
+ end
171
+
172
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
173
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
174
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
175
+ # hence you can't have attributes that aren't part of the table columns.
176
+ #
177
+ # ==== Example:
178
+ # # Instantiates a single new object
179
+ # User.new(first_name: 'Jamie')
180
+ def initialize(attributes = nil)
181
+ defaults = self.class.column_defaults.dup
182
+ defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
183
+
184
+ @attributes = self.class.initialize_attributes(defaults)
185
+ @columns_hash = self.class.column_types.dup
186
+
187
+ init_internals
188
+ init_changed_attributes
189
+ ensure_proper_type
190
+ populate_with_current_scope_attributes
191
+
192
+ assign_attributes(attributes) if attributes
193
+
194
+ yield self if block_given?
195
+ run_callbacks :initialize unless _initialize_callbacks.empty?
196
+ end
197
+
198
+ # Initialize an empty model object from +coder+. +coder+ must contain
199
+ # the attributes necessary for initializing an empty model object. For
200
+ # example:
201
+ #
202
+ # class Post < ActiveRecord::Base
203
+ # end
204
+ #
205
+ # post = Post.allocate
206
+ # post.init_with('attributes' => { 'title' => 'hello world' })
207
+ # post.title # => 'hello world'
208
+ def init_with(coder)
209
+ @attributes = self.class.initialize_attributes(coder['attributes'])
210
+ @columns_hash = self.class.column_types.merge(coder['column_types'] || {})
211
+
212
+ init_internals
213
+
214
+ @new_record = false
215
+
216
+ run_callbacks :find
217
+ run_callbacks :initialize
218
+
219
+ self
220
+ end
221
+
222
+ ##
223
+ # :method: clone
224
+ # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
225
+ # That means that modifying attributes of the clone will modify the original, since they will both point to the
226
+ # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
227
+ #
228
+ # user = User.first
229
+ # new_user = user.clone
230
+ # user.name # => "Bob"
231
+ # new_user.name = "Joe"
232
+ # user.name # => "Joe"
233
+ #
234
+ # user.object_id == new_user.object_id # => false
235
+ # user.name.object_id == new_user.name.object_id # => true
236
+ #
237
+ # user.name.object_id == user.dup.name.object_id # => false
238
+
239
+ ##
240
+ # :method: dup
241
+ # Duped objects have no id assigned and are treated as new records. Note
242
+ # that this is a "shallow" copy as it copies the object's attributes
243
+ # only, not its associations. The extent of a "deep" copy is application
244
+ # specific and is therefore left to the application to implement according
245
+ # to its need.
246
+ # The dup method does not preserve the timestamps (created|updated)_(at|on).
247
+
248
+ ##
249
+ def initialize_dup(other) # :nodoc:
250
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
251
+ self.class.initialize_attributes(cloned_attributes, :serialized => false)
252
+
253
+ @attributes = cloned_attributes
254
+ @attributes[self.class.primary_key] = nil
255
+
256
+ run_callbacks(:initialize) unless _initialize_callbacks.empty?
257
+
258
+ @changed_attributes = {}
259
+ init_changed_attributes
260
+
261
+ @aggregation_cache = {}
262
+ @association_cache = {}
263
+ @attributes_cache = {}
264
+
265
+ @new_record = true
266
+
267
+ ensure_proper_type
268
+ super
269
+ end
270
+
271
+ # Populate +coder+ with attributes about this record that should be
272
+ # serialized. The structure of +coder+ defined in this method is
273
+ # guaranteed to match the structure of +coder+ passed to the +init_with+
274
+ # method.
275
+ #
276
+ # Example:
277
+ #
278
+ # class Post < ActiveRecord::Base
279
+ # end
280
+ # coder = {}
281
+ # Post.new.encode_with(coder)
282
+ # coder # => {"attributes" => {"id" => nil, ... }}
283
+ def encode_with(coder)
284
+ coder['attributes'] = attributes
285
+ end
286
+
287
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
288
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
289
+ #
290
+ # Note that new records are different from any other record by definition, unless the
291
+ # other record is the receiver itself. Besides, if you fetch existing records with
292
+ # +select+ and leave the ID out, you're on your own, this predicate will return false.
293
+ #
294
+ # Note also that destroying a record preserves its ID in the model instance, so deleted
295
+ # models are still comparable.
296
+ def ==(comparison_object)
297
+ super ||
298
+ comparison_object.instance_of?(self.class) &&
299
+ id.present? &&
300
+ comparison_object.id == id
301
+ end
302
+ alias :eql? :==
303
+
304
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
305
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
306
+ def hash
307
+ id.hash
308
+ end
309
+
310
+ # Clone and freeze the attributes hash such that associations are still
311
+ # accessible, even on destroyed records, but cloned models will not be
312
+ # frozen.
313
+ def freeze
314
+ @attributes = @attributes.clone.freeze
315
+ self
316
+ end
317
+
318
+ # Returns +true+ if the attributes hash has been frozen.
319
+ def frozen?
320
+ @attributes.frozen?
321
+ end
322
+
323
+ # Allows sort on objects
324
+ def <=>(other_object)
325
+ if other_object.is_a?(self.class)
326
+ self.to_key <=> other_object.to_key
327
+ end
328
+ end
329
+
330
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
331
+ # attributes will be marked as read only since they cannot be saved.
332
+ def readonly?
333
+ @readonly
334
+ end
335
+
336
+ # Marks this record as read only.
337
+ def readonly!
338
+ @readonly = true
339
+ end
340
+
341
+ # Returns the connection currently associated with the class. This can
342
+ # also be used to "borrow" the connection to do database work that isn't
343
+ # easily done without going straight to SQL.
344
+ def connection
345
+ ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class")
346
+ self.class.connection
347
+ end
348
+
349
+ def connection_handler
350
+ self.class.connection_handler
351
+ end
352
+
353
+ # Returns the contents of the record as a nicely formatted string.
354
+ def inspect
355
+ # We check defined?(@attributes) not to issue warnings if the object is
356
+ # allocated but not initialized.
357
+ inspection = if defined?(@attributes) && @attributes
358
+ self.class.column_names.collect { |name|
359
+ if has_attribute?(name)
360
+ "#{name}: #{attribute_for_inspect(name)}"
361
+ end
362
+ }.compact.join(", ")
363
+ else
364
+ "not initialized"
365
+ end
366
+ "#<#{self.class} #{inspection}>"
367
+ end
368
+
369
+ # Returns a hash of the given methods with their names as keys and returned values as values.
370
+ def slice(*methods)
371
+ Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
372
+ end
373
+
374
+ def set_transaction_state(state) # :nodoc:
375
+ @transaction_state = state
376
+ end
377
+
378
+ def has_transactional_callbacks? # :nodoc:
379
+ !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
380
+ end
381
+
382
+ private
383
+
384
+ # Updates the attributes on this particular ActiveRecord object so that
385
+ # if it is associated with a transaction, then the state of the AR object
386
+ # will be updated to reflect the current state of the transaction
387
+ #
388
+ # The @transaction_state variable stores the states of the associated
389
+ # transaction. This relies on the fact that a transaction can only be in
390
+ # one rollback or commit (otherwise a list of states would be required)
391
+ # Each AR object inside of a transaction carries that transaction's
392
+ # TransactionState.
393
+ #
394
+ # This method checks to see if the ActiveRecord object's state reflects
395
+ # the TransactionState, and rolls back or commits the ActiveRecord object
396
+ # as appropriate.
397
+ #
398
+ # Since ActiveRecord objects can be inside multiple transactions, this
399
+ # method recursively goes through the parent of the TransactionState and
400
+ # checks if the ActiveRecord object reflects the state of the object.
401
+ def sync_with_transaction_state
402
+ update_attributes_from_transaction_state(@transaction_state, 0)
403
+ end
404
+
405
+ def update_attributes_from_transaction_state(transaction_state, depth)
406
+ if transaction_state && !has_transactional_callbacks?
407
+ unless @reflects_state[depth]
408
+ if transaction_state.committed?
409
+ committed!
410
+ elsif transaction_state.rolledback?
411
+ rolledback!
412
+ end
413
+ @reflects_state[depth] = true
414
+ end
415
+
416
+ if transaction_state.parent && !@reflects_state[depth+1]
417
+ update_attributes_from_transaction_state(transaction_state.parent, depth+1)
418
+ end
419
+ end
420
+ end
421
+
422
+ # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
423
+ # of the array, and then rescues from the possible NoMethodError. If those elements are
424
+ # ActiveRecord::Base's, then this triggers the various method_missing's that we have,
425
+ # which significantly impacts upon performance.
426
+ #
427
+ # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
428
+ #
429
+ # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
430
+ def to_ary # :nodoc:
431
+ nil
432
+ end
433
+
434
+ def init_internals
435
+ pk = self.class.primary_key
436
+ @attributes[pk] = nil unless @attributes.key?(pk)
437
+
438
+ @aggregation_cache = {}
439
+ @association_cache = {}
440
+ @attributes_cache = {}
441
+ @previously_changed = {}
442
+ @changed_attributes = {}
443
+ @readonly = false
444
+ @destroyed = false
445
+ @marked_for_destruction = false
446
+ @destroyed_by_association = nil
447
+ @new_record = true
448
+ @txn = nil
449
+ @_start_transaction_state = {}
450
+ @transaction_state = nil
451
+ @reflects_state = [false]
452
+ end
453
+
454
+ def init_changed_attributes
455
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
456
+ # optimistic locking) won't get written unless they get marked as changed
457
+ self.class.columns.each do |c|
458
+ attr, orig_value = c.name, c.default
459
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
460
+ end
461
+ end
462
+ end
463
+ end
@@ -0,0 +1,122 @@
1
+ module ActiveRecord
2
+ # = Active Record Counter Cache
3
+ module CounterCache
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Resets one or more counter caches to their correct value using an SQL
8
+ # count query. This is useful when adding new counter caches, or if the
9
+ # counter has been corrupted or modified directly by SQL.
10
+ #
11
+ # ==== Parameters
12
+ #
13
+ # * +id+ - The id of the object you wish to reset a counter on.
14
+ # * +counters+ - One or more association counters to reset
15
+ #
16
+ # ==== Examples
17
+ #
18
+ # # For Post with id #1 records reset the comments_count
19
+ # Post.reset_counters(1, :comments)
20
+ def reset_counters(id, *counters)
21
+ object = find(id)
22
+ counters.each do |association|
23
+ has_many_association = reflect_on_association(association.to_sym)
24
+ raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association
25
+
26
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
27
+ has_many_association = has_many_association.through_reflection
28
+ end
29
+
30
+ foreign_key = has_many_association.foreign_key.to_s
31
+ child_class = has_many_association.klass
32
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
33
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
34
+ counter_name = reflection.counter_cache_column
35
+
36
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
37
+ arel_table[counter_name] => object.send(association).count
38
+ })
39
+ connection.update stmt
40
+ end
41
+ return true
42
+ end
43
+
44
+ # A generic "counter updater" implementation, intended primarily to be
45
+ # used by increment_counter and decrement_counter, but which may also
46
+ # be useful on its own. It simply does a direct SQL update for the record
47
+ # with the given ID, altering the given hash of counters by the amount
48
+ # given by the corresponding value:
49
+ #
50
+ # ==== Parameters
51
+ #
52
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
53
+ # * +counters+ - An Array of Hashes containing the names of the fields
54
+ # to update as keys and the amount to update the field by as values.
55
+ #
56
+ # ==== Examples
57
+ #
58
+ # # For the Post with id of 5, decrement the comment_count by 1, and
59
+ # # increment the action_count by 1
60
+ # Post.update_counters 5, comment_count: -1, action_count: 1
61
+ # # Executes the following SQL:
62
+ # # UPDATE posts
63
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
64
+ # # action_count = COALESCE(action_count, 0) + 1
65
+ # # WHERE id = 5
66
+ #
67
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
68
+ # Post.update_counters [10, 15], comment_count: 1
69
+ # # Executes the following SQL:
70
+ # # UPDATE posts
71
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
72
+ # # WHERE id IN (10, 15)
73
+ def update_counters(id, counters)
74
+ updates = counters.map do |counter_name, value|
75
+ operator = value < 0 ? '-' : '+'
76
+ quoted_column = connection.quote_column_name(counter_name)
77
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
78
+ end
79
+
80
+ where(primary_key => id).update_all updates.join(', ')
81
+ end
82
+
83
+ # Increment a numeric field by one, via a direct SQL update.
84
+ #
85
+ # This method is used primarily for maintaining counter_cache columns used to
86
+ # store aggregate values. For example, a DiscussionBoard may cache posts_count
87
+ # and comments_count to avoid running an SQL query to calculate the number of
88
+ # posts and comments there are each time it is displayed.
89
+ #
90
+ # ==== Parameters
91
+ #
92
+ # * +counter_name+ - The name of the field that should be incremented.
93
+ # * +id+ - The id of the object that should be incremented or an Array of ids.
94
+ #
95
+ # ==== Examples
96
+ #
97
+ # # Increment the post_count column for the record with an id of 5
98
+ # DiscussionBoard.increment_counter(:post_count, 5)
99
+ def increment_counter(counter_name, id)
100
+ update_counters(id, counter_name => 1)
101
+ end
102
+
103
+ # Decrement a numeric field by one, via a direct SQL update.
104
+ #
105
+ # This works the same as increment_counter but reduces the column value by
106
+ # 1 instead of increasing it.
107
+ #
108
+ # ==== Parameters
109
+ #
110
+ # * +counter_name+ - The name of the field that should be decremented.
111
+ # * +id+ - The id of the object that should be decremented or an Array of ids.
112
+ #
113
+ # ==== Examples
114
+ #
115
+ # # Decrement the post_count column for the record with an id of 5
116
+ # DiscussionBoard.decrement_counter(:post_count, 5)
117
+ def decrement_counter(counter_name, id)
118
+ update_counters(id, counter_name => -1)
119
+ end
120
+ end
121
+ end
122
+ end