What's New in Edge Rails: Dirty Objects

Posted by ryan
at 7:38 PM on Monday, March 31, 2008

The ability to track whether or not your active record objects have been modified or not becomes a lot easier with the new dirty object functionality of edge rails. It’s dead simple, and pretty slick, to use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
article = Article.find(:first)
article.changed?  #=> false

# Track changes to individual attributes with
# attr_name_changed? accessor
article.title  #=> "Title"
article.title = "New Title"
article.title_changed? #=> true

# Access previous value with attr_name_was accessor
article.title_was  #=> "Title"

# See both previous and current value with attr_name_change accessor
article.title_change  #=> ["Title", "New Title"]

Beyond the clever attribute based accessor methods, you can also query to object directly for its list of all changed attributes. (Continuing from the previous example):

1
2
3
4
5
# Get a list of changed attributes
article.changed  #=> ['title']

# Get the hash of changed attributes and their previous and current values
article.changes  #=> { 'title' => ["Title", "New Title"] }

Note: Once you save a dirty object it clears out its changed state tracking and is once again considered unchanged.

1
2
3
article.changed?  #=> true
article.save  #=> true
article.changed?  #=> false

If you’re going to be modifying an attribute outside of the attr= writer, you can use attr_name_will_change! to tell the object to be aware of the change:

1
2
3
4
article = Article.find(:first)
article.title_will_change!
article.title.upcase!
article.title_change  #=> ['Title', 'TITLE']

And coming down the pipe is a feature that will make the most of this functionality – partial SQL updates that will only update attributes that have changed…

tags: ruby, rubyonrails

What's New in Edge Rails: Has One :through

Posted by ryan
at 11:59 AM on Monday, March 24, 2008

It would appear that has_one has finally grown up the stature of has_many and now has support for the :through option.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Magazine < ActiveRecord::Base
  has_many :subscriptions
end

class Subscription < ActiveRecord::Base
  belongs_to :magazine
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :subscriptions
  has_one :magazine, :through => : subscriptions, :conditions => ['subscriptions.active = ?', true]
end

The intermediate associations are properly handled when doing assignments as well:

1
2
3
@ryan.subscriptions #=> []
@ryan.magazine = Magazine.create(:name => 'Hustler')
@ryan.subscriptions #=> [<Subscription magazine_id: 1, user_id: 1 ...>]

tags: ruby, rubyonrails

What's New in Edge Rails: Has Finder Functionality

Posted by ryan
at 11:37 AM on Monday, March 24, 2008

It looks like Nick Kallen’s wildly popular has_finder plugin will be making its way into Rails 2.x in the form of named_scope. Observe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User < ActiveRecord::Base
  named_scope :active, :conditions => {:active => true}
  named_scope :inactive, :conditions => {:active => false}
  named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
end

# Standard usage
User.active    # same as User.find(:all, :conditions => {:active => true})
User.inactive # same as User.find(:all, :conditions => {:active => false})
User.recent   # same as User.find(:all, :conditions => ['created_at > ?', 1.week.ago])

# They're nest-able too!
User.active.recent
  # same as:
  # User.with_scope(:conditions => {:active => true}) do
  #   User.find(:all, :conditions => ['created_at > ?', 1.week.ago])
  # end

All the goodness you’ve come to love in has_finder is now available as named_scope – plus you get some extra goodies too. User.all is given to you for free as an alias for User.find(:all).

Advanced

For those with more discriminating needs, don’t forget some of these has_finder tidbits:

Passing Arguments

Pass in arguments to your named scopes to specify conditions (or other props) at run-time.

1
2
3
4
5
class User < ActiveRecord::Base
  named_scope :registered, lambda { |time_ago| { :conditions => ['created_at > ?', time_ago] }
end

User.registered 7.days.ago # same as User.find(:all, :conditions => ['created_at > ?', 7.days.ago])

Named Scope Extensions

Extend named scopes (in a similar fashion to association extensions).

1
2
3
4
5
6
7
8
9
10
class User < ActiveRecord::Base
  named_scope :inactive, :conditions => {:active => false} do
    def activate
      each { |i| i.update_attribute(:active, true) }
    end
  end
end

# Re-activate all inactive users
User.inactive.activate

Anonymous Scopes

You can also pass around scopes as first class objects using scoped (a named scoped provided to you for free) as a way to build hairy queries on the fly.

1
2
3
4
5
6
7
8
9
# Store named scopes
active = User.scoped(:conditions => {:active => true})
recent = User.scoped(:conditions => ['created_at > ?', 7.days.ago])

# Which can be combined
recent_active = recent.active

# And operated upon
recent_active.each { |u| ... }

named_scope is a truly great feature. If you haven’t started using it yet, do so. You won’t know how you lived without it. Major thanks goes out to Nick.

tags: ruby, rubyonrails