[ANN] Active Record 0.8.2: Inheritable callback ques

Discussion in 'Ruby' started by David Heinemeier Hansson, Jun 4, 2004.

  1. What's new in Active Record 0.8.2?
    ==================================

    Besides the overwriteable callback methods, it's now also possible to
    register callbacks through the use of the callback macros. Their main
    advantage is that the macros add behavior into a callback que that is
    kept intact down through an inheritance hierarchy. Example:

    class Topic < ActiveRecord::Base
    before_destroy :destroy_author
    end

    class Reply < Topic
    before_destroy :destroy_readers
    end

    Now, when Topic#destroy is run only destroy_author is called. When
    Reply#destroy is run both destroy_author and destroy_readers is called.
    Contrast this to the situation where we've implemented the save
    behavior through overwriteable methods:

    class Topic < ActiveRecord::Base
    def before_destroy() destroy_author end
    end

    class Reply < Topic
    def before_destroy() destroy_readers end
    end

    In that case, Reply#destroy would only run destroy_readers and _not_
    destroy_author. So use the callback macros when you want to ensure that
    a certain callback is called for the entire hierarchy and the regular
    overwriteable methods when you want to leave it up to each descendent
    to decide whether they want to call super and trigger the inherited
    callbacks.

    Additionally, these new callback macros will accept method fragments,
    which will be evaluated with the binding of the callback spot.

    All this might seem pretty hard to understand why it's really cool.
    That's until you see the first application of it:

    * Added :dependent option to has_many and has_one, which will
    automatically destroy associated objects when the holder is destroyed:

    class Album < ActiveRecord::Base
    has_many :tracks, :dependent => true
    end

    All the associated tracks are destroyed when the album is.

    Pretty neat, hu?

    Read more about inheritable callback ques:
    http://ar.rubyonrails.org/classes/ActiveRecord/Callbacks.html

    Read more about dependent option for has_many/one:
    http://ar.rubyonrails.org/classes/ActiveRecord/Associations/
    ClassMethods.html#M000005


    Also in 0.8.2:

    * Added Base.create as a factory that'll create, save, and return a new
    object in
    one step.

    * Automatically convert strings in config hashes to symbols for the
    _connection methods.
    This allows you to pass the argument hashes directly from yaml. (Luke)

    * Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock]

    * Modified block syntax to better follow our code standards outlined in
    http://rails.rubyonrails.org/show/CodingStandards

    Get the release and read more at http://activerecord.rubyonrails.org/


    Hang out with the Ruby on Rails crowd
    =====================================

    Come by the IRC channel #rubyonrails on Freenode. Design decisions are
    aired here and you'll be able to ask questions about Active Record and
    the framework in general. Oh, and we're really friendly too!


    Call for help!
    ==============

    Do you have working knowledge with and access to either Oracle, ODBC,
    Sybase, or DB2, I'd be really grateful if you would consider writing an
    adapter for Active Record. Adapters are usually just around 100 lines
    of code. You'll have three examples to look at, a well-specified
    interface[1], and almost 100 test cases to make it real easy. Luke
    Holden reports that he spent just a few hours getting SQLite and
    PostgreSQL adapters working.

    [1]
    http://ar.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/
    AbstractAdapter.html


    Active Record -- Object-relation mapping put on rails
    =====================================================

    Active Record connects business objects and database tables to create a
    persistable
    domain model where logic and data is presented in one wrapping. It's an
    implementation of the object-relational mapping (ORM) pattern by the
    same name as described by Martin Fowler:

    "An object that wraps a row in a database table or view, encapsulates
    the database access, and adds domain logic on that data."

    Active Records main contribution to the pattern is to relieve the
    original of two stunting problems: lack of associations and
    inheritance. By adding a simple domain language-like set of macros to
    describe the former and integrating the Single Table Inheritance
    pattern for the latter, Active Record narrows the gap of functionality
    between the data mapper and active record approach.

    A short rundown of the major features:

    * Automated mapping between classes and tables, attributes and columns.
    class Product < ActiveRecord::Base; end

    ...is automatically mapped to the table named "products", such as:

    CREATE TABLE products (
    id int(11) NOT NULL auto_increment,
    name varchar(255),
    PRIMARY KEY (id)
    );

    ...which again gives Product#name and Product#name=(new_name)


    * Associations between objects controlled by simple meta-programming
    macros.
    class Firm < ActiveRecord::Base
    has_many :clients
    has_one :account
    belong_to :conglomorate
    end


    * Aggregations of value objects controlled by simple meta-programming
    macros.
    class Account < ActiveRecord::Base
    composed_of :balance, :class_name => "Money",
    :mapping => %w(balance amount)
    composed_of :address,
    :mapping => [%w(address_street street),
    %w(address_city city)]
    end


    * Validation rules that can differ for new or existing objects.
    class Post < ActiveRecord::Base
    def validate # validates on both creates and updates
    errors.add_on_empty "title"
    end

    def validate_on_update
    errors.add_on_empty "password"
    end
    end


    * Callbacks as methods or ques on the entire lifecycle
    (instantiation, saving, destroying, validating, etc).

    class Person < ActiveRecord::Base
    def before_destroy # is called just before Person#destroy
    CreditCard.find(credit_card_id).destroy
    end
    end

    class Account < ActiveRecord::Base
    after_find :eager_load, 'self.class.announce(#{id})'
    end

    Learn more in link:classes/ActiveRecord/Callbacks.html


    * Observers for the entire lifecycle
    class CommentObserver < ActiveRecord::Observer
    def after_create(comment) # is called just after Comment#save
    NotificationService.send_email("", comment)
    end
    end


    * Inheritance hierarchies
    class Company < ActiveRecord::Base; end
    class Firm < Company; end
    class Client < Company; end
    class PriorityClient < Client; end


    * Transaction support on both a database and object level. The latter
    is implemented
    by using Transaction::Simple

    # Just database transaction
    Account.transaction do
    david.withdrawal(100)
    mary.deposit(100)
    end

    # Database and object transaction
    Account.transaction(david, mary) do
    david.withdrawal(100)
    mary.deposit(100)
    end


    * Direct manipulation (instead of service invocation)

    So instead of (Hibernate example):

    long pkId = 1234;
    DomesticCat pk = (DomesticCat) sess.load( Cat.class, new
    Long(pkId) );
    // something interesting involving a cat...
    sess.save(cat);
    sess.flush(); // force the SQL INSERT

    Active Record lets you:

    pkId = 1234
    cat = Cat.find(pkId)
    # something even more interesting involving a the same cat...
    cat.save


    * Database abstraction through simple adapters (~100 lines) with a
    shared connector

    ActiveRecord::Base.establish_connection:)adapter => "sqlite",
    :dbfile => "dbfile")

    ActiveRecord::Base.establish_connection(
    :adapter => "mysql",
    :host => "localhost",
    :username => "me",
    :password => "secret",
    :database => "activerecord"
    )


    * Logging support for Log4r and Logger

    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")


    Philosophy
    ==========

    Active Record attempts to provide a coherent wrapping for the
    inconvenience that is object-relational mapping. The prime directive
    for this mapping has been to minimize the amount of code needed to
    built a real-world domain model. This is made possible by relying on a
    number of conventions that make it easy for Active Record to infer
    complex relations and structures from a minimal amount of explicit
    direction.

    Convention over Configuration:
    * No XML-files!
    * Lots of reflection and run-time extension
    * Magic is not inherently a bad word

    Admit the Database:
    * Lets you drop down to SQL for odd cases and performance
    * Doesn't attempt to duplicate or replace data definitions
    David Heinemeier Hansson, Jun 4, 2004
    #1
    1. Advertising

  2. David Heinemeier Hansson wrote:
    > What's new in Active Record 0.8.2?
    > ==================================


    [snip]

    Hi,

    I just tested it and it seems really easy to link Classes to tables. However, I encountered
    a blocking problem: it seems the column that's the primary key has to be named id. From the code
    in the docs (http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057) it seems this is hardcoded.
    Is it planned to have the name of the "id column" extracted from the database, or at least to let the user specify a name. I have none of my columns name id.... :)

    I hope there's a way to do what I describe, as Active Record seems to be really cool.

    Raph
    Raphael Bauduin, Jun 4, 2004
    #2
    1. Advertising

  3. David Heinemeier Hansson wrote:

    > All this might seem pretty hard to understand why it's really cool.
    > That's until you see the first application of it:
    >
    > * Added :dependent option to has_many and has_one, which will
    > automatically destroy associated objects when the holder is destroyed:
    >
    > class Album < ActiveRecord::Base
    > has_many :tracks, :dependent => true
    > end
    >
    > All the associated tracks are destroyed when the album is.
    >
    > Pretty neat, hu?


    This all does look pretty cool, because it has the potential to make
    application development truly rapid. I do have some concerns about
    performance though. How does this approach compare with an ON DELETE
    CASCADE, for example? It seems like if you kept more of the application
    logic in the database things would run much faster. On the other hand,
    this approach also has some definite drawbacks.

    Carl
    Carl Youngblood, Jun 4, 2004
    #3
  4. Raphael Bauduin wrote:

    > I just tested it and it seems really easy to link Classes to tables.
    > However, I encountered
    > a blocking problem: it seems the column that's the primary key has to
    > be named id. From the code
    > in the docs
    > (http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057) it
    > seems this is hardcoded.
    > Is it planned to have the name of the "id column" extracted from the
    > database, or at least to let the user specify a name. I have none of
    > my columns name id.... :)


    I usually prefer to name my primary ID columns <tablename>id just to
    avoid confusion. For example, the primary key of the user table is
    called userid. On many-table JOINS it gets confusing to have a lot of
    IDs floating around. So I agree that being able to change the id column
    to something else would be handy. Another thing that some people might
    be confused about is when you have a table whose primary key consists of
    multiple columns that are combinatorially unique. In these cases I
    think the solution is to add an integer primary key and use it for the
    ID column, and then make a unique index across the fields that you wish
    to be combinatorially unique.

    Carl
    Carl Youngblood, Jun 4, 2004
    #4
  5. David Heinemeier Hansson wrote:

    > All this might seem pretty hard to understand why it's really cool.
    > That's until you see the first application of it:
    >
    > * Added :dependent option to has_many and has_one, which will
    > automatically destroy associated objects when the holder is destroyed:
    >
    > class Album < ActiveRecord::Base
    > has_many :tracks, :dependent => true
    > end
    >
    > All the associated tracks are destroyed when the album is.


    One other question: Are these operations atomic? Does a transaction get
    started before the cascading deletes occur and end when they are
    finished? Because otherwise you could have orphaned rows if the server
    gets hosed while you are still in the process of deleting something's
    children.
    Carl Youngblood, Jun 4, 2004
    #5
  6. David Heinemeier Hansson

    Kirk Haines Guest

    On Sat, 5 Jun 2004 00:41:55 +0900, Carl Youngblood wrote

    > I usually prefer to name my primary ID columns <tablename>id just to
    > avoid confusion. For example, the primary key of the user table is
    > called userid. On many-table JOINS it gets confusing to have a lot
    > of IDs floating around. So I agree that being able to change the id
    > column to something else would be handy. Another thing that some
    > people might be confused about is when you have a table whose
    > primary key consists of multiple columns that are combinatorially
    > unique. In these cases I think the solution is to add an integer
    > primary key and use it for the ID column, and then make a unique
    > index across the fields that you wish to be combinatorially unique.


    I still haven't delved into ActiveRecord's code as much as I want to, but in
    theory these things should be supportable.

    Kansas, which is similar to ActiveRecord but takes a few different
    approaches, allows things like this:

    create table jukebox (
    id int unsigned primary key,
    serial_number int unsigned unique,
    blah blah blah)

    create table albums (
    id int unsigned primary key,
    jukebox_serial int,
    blah blah blah)

    create table tracks (
    id int unsigned primary key,
    album_id int,
    blah blah blah)

    class Jukebox
    to_many:)albums, :Albums, :jukebox_serial, :serial_number)
    end

    class Albums
    to_many:)tracks, :Tracks, :album_id)
    belongs_to:)jukebox, :Jukebox, :serial_number, :jukebox_serial)
    end

    class Tracks
    belongs_to:)album, :Albums, :album_id)
    end


    So, a Jukebox has many Albums. However, instead of using Jukebox's primary
    key, serial_number on the Jukebox maps to jukebox_serial, which is simply a
    unique key, on the Album.

    Albums have many tracks, and are correlated through the primary key on Album
    and the album_id field on Track. An Album also belongs to a Jukebox, and is
    correlated via the jukebox_serial field on the Album and the serial_number
    field on the Jukebox.

    Tracks belong to an Album, and are correlated via the album_id field of the
    Track to the primary key of the Album.

    I don't yet have complete support for multiple field primary keys, but
    that's not far off. There is a release with some added documentation
    explaining the stuff above, among other things, coming hopefully by the end
    of the day.


    Kirk Haines
    Kirk Haines, Jun 4, 2004
    #6
  7. David Heinemeier Hansson

    Aredridel Guest

    --=-gY1zHbz8GZv34OhNggLD
    Content-Type: text/plain
    Content-Transfer-Encoding: 7bit

    Raphael Bauduin wrote:
    > I just tested it and it seems really easy to link Classes to tables.
    > However, I encountered
    > a blocking problem: it seems the column that's the primary key has to
    > be named id. From the code
    > in the docs
    > (http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000057) it
    > seems this is hardcoded.
    > Is it planned to have the name of the "id column" extracted from the
    > database, or at least to let the user specify a name. I have none of
    > my columns name id.... :)


    Attached is a patch that adds a primary key class method, and alters the
    code to use it:

    class User < ActiveRecord::Base
    def self.primary_key
    "userid"
    end
    end

    User.find("jdoe")

    It removes support for automatically calling to_i on key parameters, and
    instead passes them to the database as strings, so it may interpret as
    it sees fit. Also added is a little extra debugging for the internal
    calls to eval.

    Ari

    --=-gY1zHbz8GZv34OhNggLD
    Content-Disposition: attachment; filename=ar-pkey.patch
    Content-Type: text/x-patch; name=ar-pkey.patch; charset=utf-8
    Content-Transfer-Encoding: 7bit

    ? .svn
    ? Rakefile
    ? debug.log
    ? doc
    ? dev-utils/.svn
    ? diagrams/.svn
    ? diagrams/activeRecordSketch.gif
    ? examples/.svn
    ? lib/.svn
    ? lib/active_record/.svn
    ? lib/active_record/connection_adapters/.svn
    ? lib/active_record/support/.svn
    ? lib/active_record/value_objects/.svn
    ? lib/active_record/vendor/.svn
    ? test/.svn
    ? test/debug.log
    ? test/connections/.svn
    ? test/connections/dbi_mysql/.svn
    ? test/connections/mysql_ruby/.svn
    ? test/connections/native_mysql/.svn
    ? test/connections/native_postgresql/.svn
    ? test/connections/native_sqlite/.svn
    ? test/connections/ruby_mysql/.svn
    ? test/fixtures/.svn
    ? test/fixtures/accounts/.svn
    ? test/fixtures/companies/.svn
    ? test/fixtures/customers/.svn
    ? test/fixtures/db_definitions/.svn
    ? test/fixtures/developer_project/.svn
    ? test/fixtures/developers/.svn
    ? test/fixtures/developers_projects/.svn
    ? test/fixtures/projects/.svn
    ? test/fixtures/topics/.svn
    Index: dev-utils/eval_debugger.rb
    ===================================================================
    RCS file: /var/cvs/activerecord/activerecord/dev-utils/eval_debugger.rb,v
    retrieving revision 1.4
    diff -u -r1.4 eval_debugger.rb
    --- dev-utils/eval_debugger.rb 30 May 2004 15:19:55 -0000 1.4
    +++ dev-utils/eval_debugger.rb 4 Jun 2004 17:30:04 -0000
    @@ -3,7 +3,7 @@
    class Module
    alias :eek:ld_module_eval :module_eval
    def module_eval(*args, &block)
    - $stderr.puts("\n#{self.name} is 'module_eval'ing:\n#{args[0]}") if args[0]
    + puts("in #{self.name}, #{if args[1] then "file #{args[1]}" end} #{if args[2] then "on line #{args[2]}" end}:\n#{args[0]}") if args[0]
    old_module_eval(*args, &block)
    end
    end
    Index: lib/active_record/associations.rb
    ===================================================================
    RCS file: /var/cvs/activerecord/activerecord/lib/active_record/associations.rb,v
    retrieving revision 1.28
    diff -u -r1.28 associations.rb
    --- lib/active_record/associations.rb 3 Jun 2004 09:43:45 -0000 1.28
    +++ lib/active_record/associations.rb 4 Jun 2004 17:30:04 -0000
    @@ -97,14 +97,14 @@
    collection_finder = "#{collection_class_name}.find_by_sql(\"#{options[:finder_sql]}\")"
    collection_counter = "#{collection_class_name}.count_by_sql(\"#{counter_sql}\")"
    else
    - collection_finder = <<-end_eval
    + collection_finder = <<-"end_eval"
    #{collection_class_name}.find_all(
    - "#{class_primary_key_name} = \#{id}#{options[:conditions] ? " AND " + options[:conditions] : ""}",
    + "#{class_primary_key_name} = '\#{id}'#{options[:conditions] ? " AND " + options[:conditions] : ""}",
    #{options[:eek:rder] ? "\"" + options[:eek:rder] + "\"" : "nil" }
    )
    end_eval

    - collection_counter = "#{collection_class_name}.count(\"#{class_primary_key_name} = \#{id}\")"
    + collection_counter = "#{collection_class_name}.count(\"#{class_primary_key_name} = '\#{id}'\")"
    end

    has_collection_method(collection_name)
    @@ -203,12 +203,12 @@
    association_name, association_class_name, class_primary_key_name =
    associate_identification(association_id, options[:class_name], options[:foreign_key])

    - association_class_primary_key_name = options[:foreign_key] || association_class_name.downcase + "_id"
    -
    + association_class_primary_key_name = options[:foreign_key] || association_class_name.gsub(/^.*::/, '').downcase! + "_id"
    +
    if options[:remote]
    association_finder = <<-"end_eval"
    #{association_class_name}.find_first(
    - "#{class_primary_key_name} = \#{id}#{options[:conditions] ? " AND " + options[:conditions] : ""}",
    + "#{class_primary_key_name} = '\#{id}'#{options[:conditions] ? " AND " + options[:conditions] : ""}",
    #{options[:eek:rder] ? "\"" + options[:eek:rder] + "\"" : "nil" }
    )
    end_eval
    @@ -275,7 +275,7 @@
    associate_identification(association_id, options[:class_name], options[:foreign_key])


    - association_foreign_key = options[:foreign_key] || association_class_name.downcase + "_id"
    + association_foreign_key = options[:foreign_key] || association_class_name.gsub(/^.*::/, '').downcase + "_id"

    association_table_name = options[:table_name] || table_name(association_class_name)
    my_key = options[:key] || name.downcase + "_id"
    @@ -322,11 +322,11 @@
    end

    def associate_identification(association_id, association_class_name, foreign_key)
    - return association_id.id2name, (association_class_name || class_name(table_name_prefix + association_id.id2name + table_name_suffix)), (foreign_key || name.downcase + "_id")
    + return association_id.id2name, (association_class_name || class_name(table_name_prefix + association_id.id2name + table_name_suffix)), (foreign_key || name.gsub(/.*::/, '').downcase + "_id")
    end

    def collection_reader_method(collection_name, collection_finder)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{collection_name}(force_reload = false)
    if @#{collection_name}.nil? || force_reload
    @#{collection_name} = #{collection_finder}
    @@ -338,7 +338,7 @@
    end

    def has_collection_method(collection_name)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def has_#{collection_name}?(force_reload = false)
    #{collection_name}(force_reload).length > 0
    end
    @@ -346,7 +346,7 @@
    end

    def collection_count_method(collection_name, collection_counter)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{collection_name}_count(force_reload = false)
    if @#{collection_name}.nil? || force_reload
    #{collection_counter}
    @@ -358,7 +358,7 @@
    end

    def association_reader_method(association_name, association_finder)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{association_name}(force_reload = false)
    if @#{association_name}.nil? || force_reload
    @#{association_name} = #{association_finder}
    @@ -370,7 +370,7 @@
    end

    def association_comparison_method(association_name, association_class_name)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{association_name}?(comparison_object, force_reload = false)
    if comparison_object.kind_of?(#{association_class_name})
    begin
    @@ -386,7 +386,7 @@
    end

    def has_association_method(association_name)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def has_#{association_name}?(force_reload = false)
    begin
    !#{association_name}(force_reload).nil?
    @@ -398,7 +398,7 @@
    end

    def build_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{method_prefix + collection_name}(attributes = {})
    association = #{collection_class_name}.new
    association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
    @@ -408,7 +408,7 @@
    end

    def create_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def #{method_prefix + collection_name}(attributes = {})
    association = #{collection_class_name}.new
    association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
    @@ -419,17 +419,17 @@
    end

    def find_in_collection_method(collection_name, collection_class_name, class_primary_key_name, conditions = nil)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def find_in_#{collection_name}(association_id)
    - #{collection_class_name}.find_on_conditions(
    - association_id, "#{class_primary_key_name} = \#{id}#{conditions ? " AND " + conditions : ""}"
    + #{collection_class_name}.find_with_constraint(
    + association_id, "#{class_primary_key_name} = '\#{id}'#{conditions ? " AND " + conditions : ""}"
    )
    end
    end_eval
    end

    def add_association_relation(association_name, insert_sql)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def add_#{association_name}(*items)
    items.flatten.each { |item| connection.insert "#{insert_sql}" }
    @#{association_name} = nil
    @@ -438,7 +438,7 @@
    end

    def remove_association_relation(association_name, foreign_key, delete_sql)
    - module_eval <<-end_eval
    + module_eval <<-"end_eval", __FILE__, __LINE__
    def remove_#{association_name}(*items)
    if items.flatten.length < 1
    connection.delete "#{delete_sql}"
    Index: lib/active_record/base.rb
    ===================================================================
    RCS file: /var/cvs/activerecord/activerecord/lib/active_record/base.rb,v
    retrieving revision 1.30
    diff -u -r1.30 base.rb
    --- lib/active_record/base.rb 3 Jun 2004 16:50:21 -0000 1.30
    +++ lib/active_record/base.rb 4 Jun 2004 17:30:05 -0000
    @@ -98,8 +98,8 @@
    ids = [ ids ].flatten

    if ids.length > 1
    - ids_list = ids.map{ |id| id.to_i }.join(", ")
    - objects = find_all("id IN (#{ids_list})", "id")
    + ids_list = ids.map{ |id| "'#{id}'" }.join(", ")
    + objects = find_all("#{primary_key} IN (#{ids_list})", "id")

    if objects.length == ids.length
    return objects
    @@ -108,7 +108,7 @@
    end
    else
    id = ids.first
    - sql = "SELECT * FROM #{table_name} WHERE id = #{id.to_i} "
    + sql = "SELECT * FROM #{table_name} WHERE #{primary_key} = '#{id}'"
    sql << "AND type = '#{name}'" unless descents_from_active_record?

    if record = connection.select_one(sql, "#{name} Find")
    @@ -124,8 +124,8 @@
    # Example:
    # Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
    def find_on_conditions(id, conditions)
    - find_first("id = #{id.to_i} AND #{sanitize_conditions(conditions)}") ||
    - raise(RecordNotFound, "Couldn't find #{name} with ID = #{id} on the condition of #{conditions}")
    + find_first("#{primary_key} = '#{id}'} AND #{sanitize_conditions(conditions)}") ||
    + raise(RecordNotFound, "Couldn't find #{name} with #{primary_key} = #{id} on the condition of #{conditions}")
    end

    # Returns an array of all the objects that could be instantiated from the associated
    @@ -133,8 +133,9 @@
    # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
    # such as by "last_name, first_name DESC". A maximum of returned objects can be specified in +limit+. Example:
    # Project.find_all "category = 'accounts'", "last_accessed DESC", 15
    - def find_all(conditions = nil, orderings = nil, limit = nil)
    + def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
    sql = "SELECT * FROM #{table_name} "
    + sql << "#{joins} " if joins
    add_conditions!(sql, conditions)
    sql << "ORDER BY #{orderings} " unless orderings.nil?
    sql << "LIMIT #{limit} " unless limit.nil?
    @@ -286,12 +287,18 @@
    table_name_prefix + undecorated_table_name(class_name) + table_name_suffix
    end

    + # Defines the primary key field -- can be overridden in subclasses.
    + def primary_key
    + "id"
    + end
    +
    # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
    def class_name(table_name) # :nodoc:
    # remove any prefix and/or suffix from the table name
    class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)]

    class_name = class_name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
    + class_name.gsub!(/.*::/, '')

    if pluralize_table_names
    if class_name[-3,3] == "ies"
    @@ -342,7 +349,7 @@

    # Guesses the table name, but does not decorate it with prefix and suffix information.
    def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
    - table_name = class_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
    + table_name = class_name.gsub(/.*::/, '').gsub(/([a-z])([A-Z])/, '\1_\2').downcase

    if pluralize_table_names
    case table_name[-1,1]
    @@ -393,11 +400,12 @@
    # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
    # id method, which isn't being used in this context.
    def id
    - @attributes["id"]
    + @attributes[self.class.primary_key]
    end

    - def id=(value) # :nodoc:
    - @attributes["id"] = value.to_i
    + # Sets the primary ID.
    + def id=(value)
    + @attributes[self.class.primary_key] = value
    end

    # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
    @@ -415,7 +423,7 @@
    # Deletes the record in the database and freezes this instance to reflect that no changes should
    # be made (since they can't be persisted).
    def destroy
    - connection.delete("DELETE FROM #{self.class.table_name} WHERE id = #{id}", "#{self.class.name} Destroy") unless new_record?
    + connection.delete("DELETE FROM #{self.class.table_name} WHERE #{self.class.primary_key} = '#{id}'", "#{self.class.name} Destroy") unless new_record?
    freeze
    end

    @@ -457,7 +465,7 @@
    connection.update(
    "UPDATE #{self.class.table_name} " +
    "SET #{comma_pair_list(attributes_with_quotes)} " +
    - "WHERE id = #{id}",
    + "WHERE #{self.class.primary_key} = '#{id}'",
    "#{self.class.name} Update"
    )
    end
    @@ -577,4 +585,4 @@
    hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
    end
    end
    -end
    \ No newline at end of file
    +end
    Index: test/associations_test.rb
    ===================================================================
    RCS file: /var/cvs/activerecord/activerecord/test/associations_test.rb,v
    retrieving revision 1.16
    diff -u -r1.16 associations_test.rb
    --- test/associations_test.rb 3 Jun 2004 09:42:12 -0000 1.16
    +++ test/associations_test.rb 4 Jun 2004 17:30:05 -0000
    @@ -212,11 +212,12 @@
    aridridel.add_projects([ Project.find(1), Project.find(2) ])
    assert_equal 2, aridridel.projects_count
    end
    -
    - def xtest_module_spanning_associations
    +
    + def test_module_spanning_associations
    assert MyApplication::Business::Firm.find_first.has_clients?
    firm = MyApplication::Business::Firm.find_first
    + assert_nil firm.class.table_name.match('::')
    assert_equal 2, MyApplication::Business::firm.clients_count
    assert MyApplication::Billing::Account.find(1).has_firm?, "37signals account should be able to backtrack"
    end
    -end
    \ No newline at end of file
    +end

    --=-gY1zHbz8GZv34OhNggLD--
    Aredridel, Jun 4, 2004
    #7
  8. > One other question: Are these operations atomic? Does a transaction
    > get started before the cascading deletes occur and end when they are
    > finished? Because otherwise you could have orphaned rows if the
    > server gets hosed while you are still in the process of deleting
    > something's children.


    They will be in 0.8.3 ;). It's in the CVS right now. I'll probably
    follow the pattern of late and release 0.8.3 version tomorrow. Lots of
    good new stuff in it.
    --
    David Heinemeier Hansson,
    http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
    http://www.basecamphq.com/ -- Web-based Project Management
    http://www.loudthinking.com/ -- Broadcasting Brain
    http://www.nextangle.com/ -- Development & Consulting Services
    David Heinemeier Hansson, Jun 4, 2004
    #8
  9. > Attached is a patch that adds a primary key class method, and alters
    > the
    > code to use it:


    Great patch, Ari! Experimentors beware, though. This patch has a few
    bugs, but all the known ones have been ironed out in the CVS version of
    Active Record. We're adding some more tests to make sure that
    everything works as it should. All of this stuff will be in 0.8.3 as
    well.
    --
    David Heinemeier Hansson,
    http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
    http://www.basecamphq.com/ -- Web-based Project Management
    http://www.loudthinking.com/ -- Broadcasting Brain
    http://www.nextangle.com/ -- Development & Consulting Services
    David Heinemeier Hansson, Jun 4, 2004
    #9
  10. David Heinemeier Hansson

    Aredridel Guest

    On Sat, 2004-06-05 at 07:25 +0900, David Heinemeier Hansson wrote:
    > > Attached is a patch that adds a primary key class method, and alters
    > > the
    > > code to use it:

    >
    > Great patch, Ari! Experimentors beware, though. This patch has a few
    > bugs, but all the known ones have been ironed out in the CVS version of
    > Active Record. We're adding some more tests to make sure that
    > everything works as it should. All of this stuff will be in 0.8.3 as
    > well.


    Yeah, sorry for the low quality of the patch -- it's from my development
    copy, with the irrelevant changes half picked out by hand.
    Cherry-picking from patches could be a lot easier than it is with CVS...

    Ari
    Aredridel, Jun 5, 2004
    #10
  11. Couldn't the table meta data be used to find out which field(s) is / are
    part of the primary key?

    Regards,

    Peter
    Peter C. Verhage, Jun 5, 2004
    #11
  12. Peter C. Verhage wrote:
    > Couldn't the table meta data be used to find out which field(s) is / are
    > part of the primary key?


    That's what I meant by 'having the name of the "id column" extracted
    from the database'. That would be the best solution IMHO, but do all
    database drivers support that?

    If it's possible, it would really be in the Active Record way of working
    (from what I've seen): extract the most information possible directly
    from the database.

    Raph

    >
    > Regards,
    >
    > Peter
    Bauduin Raphael, Jun 5, 2004
    #12
  13. David Heinemeier Hansson

    Kirk Haines Guest

    On Sat, 5 Jun 2004 21:38:37 +0900, Bauduin Raphael wrote

    > That's what I meant by 'having the name of the "id column" extracted
    > from the database'. That would be the best solution IMHO, but do all
    > database drivers support that?
    >
    > If it's possible, it would really be in the Active Record way of
    > working
    > (from what I've seen): extract the most information possible
    > directly from the database.


    Most of them support it. That's how Kansas works.


    Kirk Haines
    Kirk Haines, Jun 5, 2004
    #13
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Saumya
    Replies:
    11
    Views:
    4,247
    Datta Patil
    Mar 2, 2004
  2. Fabiano Sidler

    Why are so many built-in types inheritable?

    Fabiano Sidler, Mar 18, 2006, in forum: Python
    Replies:
    17
    Views:
    481
    Antoon Pardon
    Mar 31, 2006
  3. Indraseena

    Making base class non-inheritable

    Indraseena, Jun 22, 2006, in forum: C++
    Replies:
    6
    Views:
    472
    Indraseena
    Jun 26, 2006
  4. Hicham Mouline

    friendship not inheritable

    Hicham Mouline, Nov 14, 2008, in forum: C++
    Replies:
    4
    Views:
    276
    Hicham Mouline
    Nov 17, 2008
  5. ara.t.howard
    Replies:
    0
    Views:
    99
    ara.t.howard
    Oct 12, 2009
Loading...

Share This Page