Path: | vendor/rails/activerecord/CHANGELOG |
Last Update: | Thu Aug 04 22:20:26 UTC 2005 |
*1.11.1* (11 July, 2005)
*1.11.0* (6 July, 2005)
class Project has_and_belongs_to_many :developers, :before_add => :evaluate_velocity def evaluate_velocity(developer) ... end end
..raising an exception will cause the object not to be added (or removed, with before_remove).
Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
…should instead be:
Developer.find( :all, :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', :conditions => 'project_id=1' )
david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")] david.save
If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new project is saved when david.save is called.
Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
david.project_ids = [1, 5, 7]
fixtures :web_sites def test_something assert_equal "Ruby on Rails", web_sites(:rubyonrails).name end
Conditional validations such as the following are made possible:
validates_numericality_of :income, :if => :employed?
Conditional validations can also solve the salted login generator problem:
validates_confirmation_of :password, :if => :new_password?
Using blocks:
validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
# SELECT * FROM topics WHERE title IN ('First', 'Second') Topic.find_all_by_title(["First", "Second"])
development: adapter: postgresql database: rails_development host: localhost username: postgres password: encoding: UTF8 min_messages: ERROR
*1.10.1* (20th April, 2005)
*1.10.0* (19th April, 2005)
for post in Post.find(:all, :limit => 100) puts "Post: " + post.title puts "Written by: " + post.author.name puts "Last comment on: " + post.comments.first.created_on end
This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
…and the number of database queries needed is now 1.
Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC") Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC") Person.find(:first, :order => "created_on DESC", :offset => 5) Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50) Person.find(:all, :offset => 10, :limit => 10)
This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with the added feature that you can select the children and all of it's descendants with a single query. A good use case for this is a threaded post system, where you want to display every reply to a comment without multiple selects.
*1.9.1* (27th March, 2005)
*1.9.0* (22th March, 2005)
Developer.find_all nil, 'id ASC', 5 # return the first five developers Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
This doesn’t yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
*1.8.0* (7th March, 2005)
* +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+ objects that should be inspected to determine which attributes triggered the errors. * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method. You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
class Account < ActiveRecord::Base has_one :credit_card, :dependent => true end class CreditCard < ActiveRecord::Base belongs_to :account end account.credit_card # => returns existing credit card, lets say id = 12 account.credit_card = CreditCard.create("number" => "123") account.save # => CC with id = 12 is destroyed
Validates whether the value of the specified attribute is numeric by trying to convert it to a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true). class Person < ActiveRecord::Base validates_numericality_of :value, :on => :create end Configuration options: * <tt>message</tt> - A custom error message (default is: "is not a number") * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update) * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
*1.7.0* (24th February, 2005)
1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple and safe way of passing table-specific sequence information to the adapter.) 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to resort to some hacks to get data converted to Date or Time in Ruby. If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time. This is nasty - but if you use Duck Typing you'll probably not care very much. In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is valid - too many databases use DATE for both. Timezones and sub-second precision on timestamps are not supported. 3. Default values that are functions (such as "SYSDATE") are not supported. This is a restriction of the way active record supports default values. 4. Referential integrity constraints are not fully supported. Under at least some circumstances, active record appears to delete parent and child records out of sequence and out of transaction scope. (Or this may just be a problem of test setup.)
The OCI8 driver can be retrieved from rubyforge.org/projects/ruby-oci8/
1. Created a new columns method that is much cleaner. 2. Corrected a problem with the select and select_all methods that didn't account for the LIMIT clause being passed into raw SQL statements. 3. Implemented the string_to_time method in order to create proper instances of the time class. 4. Added logic to the simplified_type method that allows the database to specify the scale of float data. 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
class Person < ActiveRecord::Base validates_each :first_name, :last_name do |record, attr| record.errors.add attr, 'starts with z.' if attr[0] == ?z end end
class Person < ActiveRecord::Base validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID } end
class Project < ActiveRecord::Base primary_key "sysid" table_name "XYZ_PROJECT" inheritance_column { original_inheritance_column + "_id" } end
*1.6.0* (January 25th, 2005)
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} } Person.update(people.keys, people.values)
*1.5.1* (January 18th, 2005)
*1.5.0* (January 17th, 2005)
class Book < ActiveRecord::Base has_many :pages belongs_to :library validates_associated :pages, :library end
== Unsaved objects and associations You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be aware of, mostly involving the saving of associated objects. === One-to-one associations * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in order to update their primary keys - except if the parent object is unsaved (new_record? == true). * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment is cancelled. * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below). * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does not save the parent either. === Collections * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object (the owner of the collection) is not yet stored in the database. * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false. * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below). * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
Before: class ListSweeper < ActiveRecord::Base def self.observed_class() [ List, Item ] end After: class ListSweeper < ActiveRecord::Base observes List, Item end
Before: topic.update_attribute(:approved, !approved?) After : topic.toggle!(:approved)
page.views # => 1 page.increment!(:views) # executes an UPDATE statement page.views # => 2 page.increment(:views).increment!(:views) page.views # => 4
*1.4.0* (January 4th, 2005)
p1 = Person.find(1) p2 = Person.find(1) p1.first_name = "Michael" p1.save p2.first_name = "should fail" p2.save # Raises a ActiveRecord::StaleObjectError
You‘re then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, or otherwise apply the business logic needed to resolve the conflict.
384 [Michael Koziarski]
It’s also possible to use multiple attributes in the same find by separating them with "and", so you get finders like Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing Person.find_first(["user_name = ? AND password = ?", user_name, password]), you just do Person.find_by_user_name_and_password(user_name, password).
While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like Payment.find_all_by_amount(50) that is turned into Payment.find_all(["amount = ?", 50]). This is something not as equally useful, though, as it’s not possible to specify the order in which the objects are returned.
Before: before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" }) After: before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
*1.3.0* (December 23, 2004)
before: require_association 'person' class Employee < Person end after: class Employee < Person end
This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
Before: person.attributes = @params["person"] person.save Now: person.update_attributes(@params["person"])
*1.2.0*
class Person < ActiveRecord::Base validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!" validates_inclusion_of :age, :in=>0..99 end
class TodoItem < ActiveRecord::Base acts_as_list :scope => :todo_list_id belongs_to :todo_list end
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Company.inheritance_column to use another column for that information.
Before: errors.add(:name, "must be shorter") if name.size > 10 errors.on(:name) # => "must be shorter" errors.on("name") # => nil After: errors.add(:name, "must be shorter") if name.size > 10 errors.on(:name) # => "must be shorter" errors.on("name") # => "must be shorter"
class Person < ActiveRecord::Base validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create end
Validates that the specified attribute matches the length restrictions supplied in either: - configuration[:minimum] - configuration[:maximum] - configuration[:is] - configuration[:within] (aka. configuration[:in]) Only one option can be used at a time. class Person < ActiveRecord::Base validates_length_of :first_name, :maximum=>30 validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind" validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character" validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me." end
class Person < ActiveRecord::Base validates_uniqueness_of :user_name end
When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
Model: class Person < ActiveRecord::Base validates_confirmation_of :password end View: <%= password_field "person", "password" %> <%= password_field "person", "password_confirmation" %> The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual. It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
class Person < ActiveRecord::Base validates_acceptance_of :terms_of_service end
The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
NOTE: The agreement is considered valid if it’s set to the string "1". This makes it easy to relate it to an HTML checkbox.
class Person < ActiveRecord::Base validate { |record| record.errors.add("name", "too short") unless name.size > 10 } validate { |record| record.errors.add("name", "too long") unless name.size < 20 } validate_on_create :validate_password private def validate_password errors.add("password", "too short") unless password.size > 6 end end
Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20] Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
<tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }])
Before:
find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])] find_first([ "firm_id = %s", firm_id ])] # unsafe!
After:
find_first([ "user_name = ? AND password = ?", user_name, password ])] find_first([ "firm_id = ?", firm_id ])]
NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the auto-loading associations.
david: id: 1 name: David jamis: id: 2 name: Jamis <% for digit in 3..10 %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> <% end %>
fixtures/developers/fixtures.yaml fixtures/accounts/fixtures.yaml
…you now need to do:
fixtures/developers.yaml fixtures/accounts.yaml
name: david data: id: 1 name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development --- name: steve data: id: 2 name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard
…to:
david: id: 1 name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development steve: id: 2 name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard
The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
*1.1.0* (34)
class FixturesTest < Test::Unit::TestCase fixtures :developers # you can add more with comma separation def test_developers assert_equal 3, @developers.size # the container for all the fixtures is automatically set assert_kind_of Developer, @david # works like @developers["david"].find assert_equal "David Heinemeier Hansson", @david.name end end
post.categories.push_with_attributes(category, :added_on => Date.today) post.categories.first.added_on # => Date.today
NOTE: The categories table doesn’t have a added_on column, it’s the categories_post join table that does!
[ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
Before: Document.find(@documents["first"]["id"]) After : @documents["first"].find
*1.0.0 (35)*
Project#milestones_count => Project#milestones.size Project#build_to_milestones => Project#milestones.build Project#create_for_milestones => Project#milestones.create Project#find_in_milestones => Project#milestones.find Project#find_all_in_milestones => Project#milestones.find_all
class User < ActiveRecord::Base serialize :settings end
This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify an optional :class_name option that’ll raise an exception if a serialized object is retrieved as a descendent of a class not in the hierarchy. Example:
class User < ActiveRecord::Base serialize :settings, :class_name => "Hash" end user = User.create("settings" => %w( one two three )) User.find(user.id).settings # => raises SerializationTypeMismatch
This now works: log { ... }.map { ... } Instead of doing: result = [] log { result = ... } result.map { ... }
Project.benchmark("Creating project") do project = Project.create("name" => "stuff") project.create_manager("name" => "David") project.milestones << Milestone.find_all end
*0.9.5*
Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used to guess the table name from even when called on Reply. The guessing rules are as follows: * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table. * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies", so a Category class becomes a categories table. * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table. * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table. * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table. * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table. * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table. * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table. * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table. * Class name ends in an "s": No additional characters are added or removed. * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table. * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table. Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended. So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts". You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a "mice" table. Example: class Mouse < ActiveRecord::Base def self.table_name() "mice" end end
This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb.
class Firm < ActiveRecord::Base has_many :clients end firm.id # => 1 firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000
[Requested by Dave Thomas]
class Person < ActiveRecord::Base protected def validation errors.add_on_boundry_breaking "password", 3..20 end end
This will add an error to the tune of "is too short (min is 3 characters)" or "is too long (min is 20 characters)" if the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings.
*0.9.4*
*0.9.3*
If set to true all the associated object are deleted in one SQL statement without having their before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
*0.9.2*
*0.9.1*
class Event < ActiveRecord::Base has_one_and_belongs_to_many :sponsors end class Sponsor < ActiveRecord::Base has_one_and_belongs_to_many :sponsors end
Earlier, you’d have to use synthetic methods for creating associations between two objects of the above class:
roskilde_festival.add_to_sponsors(carlsberg) roskilde_festival.remove_from_sponsors(carlsberg) nike.add_to_events(world_cup) nike.remove_from_events(world_cup)
Now you can use regular array-styled methods:
roskilde_festival.sponsors << carlsberg roskilde_festival.sponsors.delete(carlsberg) nike.events << world_cup nike.events.delete(world_cup)
class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :post end
You could do something like:
funny_comment.has_post? # => true announcement.comments.delete(funny_comment) funny_comment.has_post? # => false
*0.9.0*
class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :post end
Iterating over 100 posts like this:
<% for post in @posts %> <%= post.title %> has <%= post.comments_count %> comments <% end %>
Will generate 100 SQL count queries — one for each call to post.comments_count. If you instead add a "comments_count" int column to the posts table and rewrite the comments association macro with:
class Comment < ActiveRecord::Base belongs_to :post, :counter_cache => true end
Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life with the object it’s specified to belong with and is destroyed like that as well. Typically objects where you would also specify :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another), you’ll have to manage the counter yourself.
class Project < ActiveRecord::Base has_one :manager end class Manager < ActiveRecord::Base belongs_to :project end
Earlier, assignments would work like following regardless of which way the assignment told the best story:
active_record.manager_id = david.id
Now you can do it either from the belonging side:
david.project = active_record
…or from the having side:
active_record.manager = david
If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the project_id attribute on david would be set to the id of active_record, then david would be saved.
class Project < ActiveRecord::Base has_many :milestones end class Milestone < ActiveRecord::Base belongs_to :project end
Earlier, assignments would work like following regardless of which way the assignment told the best story:
deadline.project_id = active_record.id
Now you can do it either from the belonging side:
deadline.project = active_record
…or from the having side:
active_record.milestones << deadline
The milestone is automatically saved with the new foreign key.
user = User.find(1) user.preferences = { "background" => "black", "display" => large } user.save User.find(1).preferences # => { "background" => "black", "display" => large }
Please note that this method should only be used when you don’t care about representing the object in proper columns in the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through aggregations than this new option.
class Song < ActiveRecord::Base # Uses an integer of seconds to hold the length of the song def length=(minutes) write_attribute("length", minutes * 60) end def length read_attribute("length") / 60 end end
The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly. This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think you know what you’re doing and aren’t afraid of future consequences, this is an option.
*0.8.4*
Reflection
Misc
# Instantiate objects for all attribute classes that needs more than one constructor parameter. This is done # by calling new on the column type or aggregation type (through composed_of) object with these parameters. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, # s for String, and a for Array.
This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day.
*0.8.3*
Transactions
Mapping
User.find "jdoe" Product.find "PDKEY-INT-12"
class Project < ActiveRecord::Base def self.primary_key() "project_id" end end
class MyApplication::Account < ActiveRecord::Base has_many :clients # will look for MyApplication::Client has_many :interests, :class_name => "Business::Interest" # will look for Business::Interest end
class MyApplication::Account < ActiveRecord::Base end
Misc
class AuditObserver < ActiveRecord::Observer def self.observed_class() Account end def after_update(account) AuditTrail.new(account, "UPDATED") end end
[Suggested by Gavin Sinclair]
person = Person.new do |p| p.name = 'Freddy' p.age = 19 end
[Suggested by Gavin Sinclair]
*0.8.2*
class Topic < ActiveRecord::Base before_destroy :destroy_author, 'puts "I'm an inline fragment"' end
Learn more in classes/ActiveRecord/Callbacks.html
class Album < ActiveRecord::Base has_many :tracks, :dependent => true end
All the associated tracks are destroyed when the album is.
*0.8.1*
NOTE: This requires all *_connections to be updated! Read more in: ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081
*0.8.0*
*0.7.6*