Permalink
Browse files

Remove ActiveRecord::Model

In the end I think the pain of implementing this seamlessly was not
worth the gain provided.

The intention was that it would allow plain ruby objects that might not
live in your main application to be subclassed and have persistence
mixed in. But I've decided that the benefit of doing that is not worth
the amount of complexity that the implementation introduced.
  • Loading branch information...
1 parent a27b517 commit 9e4c41c903e8e58721f2c41776a8c60ddba7a0a9 @jonleighton jonleighton committed Oct 26, 2012
Showing with 232 additions and 642 deletions.
  1. +0 −28 activerecord/CHANGELOG.md
  2. +0 −1 activerecord/lib/active_record.rb
  3. +1 −1 activerecord/lib/active_record/associations/alias_tracker.rb
  4. +1 −1 activerecord/lib/active_record/attribute_methods.rb
  5. +2 −6 activerecord/lib/active_record/attribute_methods/dirty.rb
  6. +2 −7 activerecord/lib/active_record/attribute_methods/read.rb
  7. +5 −11 activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
  8. +43 −3 activerecord/lib/active_record/base.rb
  9. +4 −4 activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
  10. +0 −1 activerecord/lib/active_record/connection_handling.rb
  11. +71 −73 activerecord/lib/active_record/core.rb
  12. +3 −5 activerecord/lib/active_record/explain.rb
  13. +1 −1 activerecord/lib/active_record/fixtures.rb
  14. +12 −29 activerecord/lib/active_record/inheritance.rb
  15. +2 −6 activerecord/lib/active_record/locking/optimistic.rb
  16. +0 −167 activerecord/lib/active_record/model.rb
  17. +23 −24 activerecord/lib/active_record/model_schema.rb
  18. +2 −6 activerecord/lib/active_record/nested_attributes.rb
  19. +5 −6 activerecord/lib/active_record/railtie.rb
  20. +1 −1 activerecord/lib/active_record/relation/finder_methods.rb
  21. +3 −3 activerecord/lib/active_record/relation/predicate_builder.rb
  22. +1 −1 activerecord/lib/active_record/result.rb
  23. +2 −9 activerecord/lib/active_record/serialization.rb
  24. +2 −3 activerecord/lib/active_record/timestamp.rb
  25. +3 −3 activerecord/test/cases/adapter_test.rb
  26. +11 −11 activerecord/test/cases/adapters/mysql/connection_test.rb
  27. +6 −6 activerecord/test/cases/adapters/mysql2/connection_test.rb
  28. +0 −2 activerecord/test/cases/attribute_methods/read_test.rb
  29. +0 −14 activerecord/test/cases/base_test.rb
  30. +2 −2 activerecord/test/cases/connection_adapters/connection_handler_test.rb
  31. +4 −4 activerecord/test/cases/defaults_test.rb
  32. +0 −133 activerecord/test/cases/inclusion_test.rb
  33. +1 −10 activerecord/test/cases/inheritance_test.rb
  34. +2 −2 activerecord/test/cases/multiple_db_test.rb
  35. +9 −9 activerecord/test/cases/pooled_connections_test.rb
  36. +2 −2 activerecord/test/cases/primary_keys_test.rb
  37. +3 −3 activerecord/test/cases/unconnected_test.rb
  38. +0 −35 activerecord/test/models/teapot.rb
  39. +0 −6 activerecord/test/schema/schema.rb
  40. +3 −3 activerecord/test/support/connection.rb
@@ -926,34 +926,6 @@
*Aaron Patterson*
-* Added the `ActiveRecord::Model` module which can be included in a
- class as an alternative to inheriting from `ActiveRecord::Base`:
-
- class Post
- include ActiveRecord::Model
- end
-
- Please note:
-
- * Up until now it has been safe to assume that all AR models are
- descendants of `ActiveRecord::Base`. This is no longer a safe
- assumption, but it may transpire that there are areas of the
- code which still make this assumption. So there may be
- 'teething difficulties' with this feature. (But please do try it
- and report bugs.)
-
- * Plugins & libraries etc that add methods to `ActiveRecord::Base`
- will not be compatible with `ActiveRecord::Model`. Those libraries
- should add to `ActiveRecord::Model` instead (which is included in
- `Base`), or better still, avoid monkey-patching AR and instead
- provide a module that users can include where they need it.
-
- * To minimise the risk of conflicts with other code, it is
- advisable to include `ActiveRecord::Model` early in your class
- definition.
-
- *Jon Leighton*
-
* PostgreSQL hstore records can be created.
*Aaron Patterson*
@@ -43,7 +43,6 @@ module ActiveRecord
autoload :Integration
autoload :Migration
autoload :Migrator, 'active_record/migration'
- autoload :Model
autoload :ModelSchema
autoload :NestedAttributes
autoload :Observer
@@ -8,7 +8,7 @@ class AliasTracker # :nodoc:
attr_reader :aliases, :table_joins, :connection
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
+ def initialize(connection = Base.connection, table_joins = [])
@aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
@table_joins = table_joins
@connection = connection
@@ -59,7 +59,7 @@ def instance_method_already_implemented?(method_name)
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
end
- if [Base, Model].include?(active_record_super)
+ if superclass == Base
super
else
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
@@ -2,11 +2,6 @@
require 'active_support/deprecation'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :partial_writes, instance_accessor: false
- self.partial_writes = true
- end
-
module AttributeMethods
module Dirty # :nodoc:
extend ActiveSupport::Concern
@@ -18,7 +13,8 @@ module Dirty # :nodoc:
raise "You cannot include Dirty after Timestamp"
end
- config_attribute :partial_writes
+ class_attribute :partial_writes, instance_writer: false
+ self.partial_writes = true
def self.partial_updates=(v); self.partial_writes = v; end
def self.partial_updates?; partial_writes?; end
@@ -1,16 +1,13 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
- end
-
module AttributeMethods
module Read
extend ActiveSupport::Concern
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- config_attribute :attribute_types_cached_by_default
+ class_attribute :attribute_types_cached_by_default, instance_writer: false
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
end
module ClassMethods
@@ -79,8 +76,6 @@ def cacheable_column?(column)
end
end
- ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
# it has been typecast (for example, "2004-12-12" in a data column is cast
# to a date object, like Date.new(2004, 12, 12)).
@@ -1,13 +1,4 @@
-
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :time_zone_aware_attributes, instance_accessor: false
- self.time_zone_aware_attributes = false
-
- mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
- self.skip_time_zone_conversion_for_attributes = []
- end
-
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -28,8 +19,11 @@ def type
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, global: true
- config_attribute :skip_time_zone_conversion_for_attributes
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
+ self.time_zone_aware_attributes = false
+
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
+ self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
@@ -321,8 +321,48 @@ module ActiveRecord #:nodoc:
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- include ActiveRecord::Model
+ extend ActiveModel::Observing::ClassMethods
+ extend ActiveModel::Naming
+
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend ConnectionHandling
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+ extend ConnectionHandling
@toretore
toretore Nov 11, 2012

I was just browsing through this code and noticed ConnectionHandling is extended twice. Not sure if that's intentional, but it seemed weird and I thought I'd say something.

@carlosantoniodasilva
carlosantoniodasilva Nov 11, 2012 Ruby on Rails member

Fixed in 2f3d81c, thanks for catching this!

+
+ include Persistence
+ include ReadonlyAttributes
+ include ModelSchema
+ include Inheritance
+ include Scoping
+ include Sanitization
+ include AttributeAssignment
+ include ActiveModel::Conversion
+ include Integration
+ include Validations
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
+ include AttributeMethods
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
+ include Associations
+ include ActiveModel::SecurePassword
+ include AutosaveAssociation
+ include NestedAttributes
+ include Aggregations
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
+ include Core
end
-end
-ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Model::DeprecationProxy.new)
+ ActiveSupport.run_load_hooks(:active_record, Base)
+end
@@ -441,11 +441,11 @@ def release(conn)
end
def new_connection
- ActiveRecord::Model.send(spec.adapter_method, spec.config)
+ Base.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Model.connection_id ||= Thread.current.object_id
+ Base.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -567,10 +567,10 @@ def retrieve_connection_pool(klass)
class_to_pool[klass] ||= begin
until pool = pool_for(klass)
klass = klass.superclass
- break unless klass < ActiveRecord::Tag
+ break unless klass <= Base
end
- class_to_pool[klass] = pool || pool_for(ActiveRecord::Model)
+ class_to_pool[klass] = pool
end
end
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ConnectionHandling
# Establishes the connection to the database. Accepts a hash as input where
@@ -4,73 +4,6 @@
module ActiveRecord
ActiveSupport.on_load(:active_record_config) do
- ##
- # :singleton-method:
- #
- # Accepts a logger conforming to the interface of Log4r which is then
- # passed on to any new database connections made and which can be
- # retrieved on both a class and instance level by calling +logger+.
- mattr_accessor :logger, instance_accessor: false
-
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- mattr_accessor :configurations, instance_accessor: false
- self.configurations = {}
-
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- mattr_accessor :default_timezone, instance_accessor: false
- self.default_timezone = :utc
-
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- mattr_accessor :schema_format, instance_accessor: false
- self.schema_format = :ruby
-
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- mattr_accessor :timestamped_migrations, instance_accessor: false
- self.timestamped_migrations = true
-
- mattr_accessor :connection_handler, instance_accessor: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
-
- mattr_accessor :dependent_restrict_raises, instance_accessor: false
- self.dependent_restrict_raises = true
end
module Core
@@ -79,12 +12,71 @@ module Core
included do
##
# :singleton-method:
- # The connection handler
- config_attribute :connection_handler
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
+ mattr_accessor :logger, instance_writer: false
- %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
- config_attribute name, global: true
- end
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_writer: false
+ self.configurations = {}
+
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_writer: false
+ self.default_timezone = :utc
+
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_writer: false
+ self.schema_format = :ruby
+
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_writer: false
+ self.timestamped_migrations = true
+
+ class_attribute :connection_handler, instance_writer: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ mattr_accessor :dependent_restrict_raises, instance_writer: false
+ self.dependent_restrict_raises = true
end
module ClassMethods
@@ -139,7 +131,13 @@ def arel_table
# Returns the Arel engine.
def arel_engine
- @arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine
+ @arel_engine ||= begin
+ if Base == self || connection_handler.retrieve_connection_pool(self)
+ self
+ else
+ superclass.arel_engine
+ end
+ end
end
private
Oops, something went wrong. Retry.

27 comments on commit 9e4c41c

@brainopia

Few alternative ORMs like Mongoid use ActiveModel pretty heavily 😭

@guilleiguaran
Member

@brainopia I don't understand your comment, ActiveModel isn't going to anywhere 😄

@brainopia

Oh, I've misread commit title, my bad 👯

@agis-
agis- commented on 9e4c41c Oct 27, 2012

Ahhh 😿

@parndt
parndt commented on 9e4c41c Oct 28, 2012

RIP

@blaix
blaix commented on 9e4c41c Oct 29, 2012

💔

@sobrinho

💔

@ralugli

=,(

@ozeias
ozeias commented on 9e4c41c Oct 29, 2012

👎

@tinogomes

😟

@Spaceghost

You're a smart cookie @jonleighton.

@ikennaokpala

Finally!!

@zires
zires commented on 9e4c41c Nov 7, 2012

💔

@ip82
ip82 commented on 9e4c41c Nov 9, 2012

Nooooooo

@joneslee85

🙉

@bwalsh
bwalsh commented on 9e4c41c Nov 12, 2012

I’ve been using the techniques below in yehuda's article successfully.

http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/

What impact does this have with “extend ActiveModel::……… “

@carlosantoniodasilva

@bwalsh No impact actually, this is removing the ability of including ActiveRecord::Model instead of inheriting from ActiveRecord::Base. Nothing in changing from ActiveModel and what Yehuda describes on his post :).

@bf4
bf4 commented on 9e4c41c May 24, 2013

I was actually thinking of doing something like this by creating a 'BasicRecord' that is a subclass of ActiveRecord::Base that contains all the current AR logic except for the ORM/persistence parts. i.e. separate out the Rails data model from the database mapping in a pretty seamless way. Does that now seem like a bad code experiment?

@guilleiguaran
Member

@bf4 sounds interesting, PDI (Please Do Investigate) and try creating it as plugin 😄

@rafaelfranca
Member

@bf4 @guilleiguaran this sounds to me like Active Model.

@guilleiguaran
Member

@rafaelfranca lol, you're right 😄

@bf4
bf4 commented on 9e4c41c May 24, 2013

Maybe I missed it, but ActiveModel is an api that can be put together, but there's no actual 'This ActiveRecord::BasicRecord is ActiveRecord::Base without an persistence concerns. Where ActiveRecord::Base is maybe just ActiveRecord::BasicRecord with the mixin ActiveRecord::ORM' i.e there's no one module to include or class to subclass that implements all AR::Base stuff except persistence

BTW, @guilleiguaran when you said as a plugin, did you mean an engine outside of the Rails project?

@rafaelfranca
Member

Active Model is also an API, but it is a set of modules that add behavior to Active Record.

Things like Validation, Callbacks, etc are defined at Active Model.

So whatever what this Basic Record should include it should be in Active Model, so every ORM can use it.

@steveklabnik
Member
@bf4
bf4 commented on 9e4c41c May 25, 2013

Ok, I didn't realize there's now an ActiveModel::Model in master/Rails 4 which does what I had in mind for use cases. It's pretty exciting it's already there :) Glad I read that code before I got too far along.

That said, I wonder if there's any reason the same code would workn't in Rails 3.x. It would be convenient not to need the boilerplate.

Please sign in to comment.