Skip to content
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
jonleighton committed Oct 26, 2012
1 parent a27b517 commit 9e4c41c903e8e58721f2c41776a8c60ddba7a0a9
Show file tree
Hide file tree
Showing 40 changed files with 232 additions and 642 deletions.
@@ -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

This comment has been minimized.

Copy link
@toretore

toretore Nov 11, 2012

Contributor

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.

This comment has been minimized.

Copy link
@carlosantoniodasilva

carlosantoniodasilva Nov 11, 2012

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

27 comments on commit 9e4c41c

@brainopia
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few alternative ORMs like Mongoid use ActiveModel pretty heavily 😭

@guilleiguaran
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@brainopia
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@agis
Copy link
Contributor

@agis agis commented on 9e4c41c Oct 27, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh 😿

@parndt
Copy link
Contributor

@parndt parndt commented on 9e4c41c Oct 28, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RIP

@blaix
Copy link

@blaix blaix commented on 9e4c41c Oct 29, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💔

@sobrinho
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💔

@ralugli
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=,(

@ozeias
Copy link

@ozeias ozeias commented on 9e4c41c Oct 29, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎

@tinogomes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😟

@tinogomes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nooooooooo

@Spaceghost
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're a smart cookie @jonleighton.

@ikennaokpala
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally!!

@zires
Copy link
Contributor

@zires zires commented on 9e4c41c Nov 7, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💔

@ip82
Copy link

@ip82 ip82 commented on 9e4c41c Nov 9, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nooooooo

@milushov
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:octocat:

@runlevel5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙉

@bwalsh
Copy link

@bwalsh bwalsh commented on 9e4c41c Nov 12, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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
Copy link
Contributor

@bf4 bf4 commented on 9e4c41c May 24, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@rafaelfranca
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@guilleiguaran
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rafaelfranca lol, you're right 😄

@bf4
Copy link
Contributor

@bf4 bf4 commented on 9e4c41c May 24, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

@steveklabnik steveklabnik commented on 9e4c41c May 24, 2013 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bf4
Copy link
Contributor

@bf4 bf4 commented on 9e4c41c May 25, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.