On When Create Is Not Like Create

July 14, 2011 § Leave a comment

I recently ran into an interesting issue in ActiveRecord while trying to set default values on an object using the after_initialize callback.  One would think the following blocks of code would be equivalent:

# new, save
Product.new(:name => "Awesome Product").save

# new w/ block, save
product = Product.new do |p|
  p.title = "Awesome Product"
end
product.save

# create
Product.create(:title => "Awesome Product")

# create w/ block
Product.create do |p|
  p.title = "Awesome Product"
end

In 99.9% of cases this is going to be true.  The last one, create with a block, however, can potentially cause you some problems if you are using after_initialize. The problem arises when you use after_initialize to set default values for attributes are are dependent on other attributes. Let us consider our Awesome Product has two more attributes, msrp and wholesale_price, that are tied to each other. If we have one of them we can always determine what the other should be. In this case, there wouldn’t really be a reason set both of them when creating a new object. Just set one and let the other one get set automatically.

For our example we’ll say, msrp = 2 * wholesale_price. You might use an after_initialize that looks something like this:

def after_initialize
  # set wholesale_price based on msrp
  if !msrp.nil? && wholesale_price.nil?
    self.wholesale_price = msrp / 2
  # set msrp based on wholesale_price
  elsif msrp.nil? && !wholesale_price.nil?
    self.msrp = wholesale_price * 2
  end
end

We can instantiate an object like this:

product = Product.new(:name => "Awesome Product", :msrp => 20)
 => <Product ...>
product.save
 => true
product.msrp
 => 20
product.wholesale
 => 10

Everything is working as it should. Now let’s use create instead of new and save.

product = Product.create(:name => "Awesome Product", :msrp => 20)
 => true
product.msrp
 => 20
product.wholesale
 => 10

Still works just fine. Now create with a block:

product = Product.create do |p|
  p.name = "Awesome Product"
  p.msrp => 20
end
 => true
product.msrp
 => 20
product.wholesale
 => nil

Uh, oh… Why didn’t wholesale_price didn’t get set? Take a look at the implementation of create in ActiveRecord::Base.

def create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes)
    yield(object) if block_given?
    object.save
    object
  end
end

Notice in the else block that a new object is created and then the block is yielded. This means that the after_initialize callback is run on the instantiated object BEFORE the block code is run. msrp is not set yet when after_initialize is run, so wholesale_price can’t be set. create without a block work fine because it is literally the same as using new and save.

TL;DR – after_initialize runs before the block code when using create and a block. Be careful when using after_initialize to set default values for attributes that depend on other attributes.

Advertisements

On Cross Subdomain Cookies

July 12, 2011 § 1 Comment

The first Ruby gem I ever wrote was tld-cookies.  While it is very poorly named, probably should have been called root-domain-cookies or something like that, it adds a nice little bit of functionality to the Rails 3 cookie jars.

One of the things about Rails 3 that I thought was really cool, was the way cookies were accessed. It’s not a big and fancy piece of code, but to me it is just a slick way to do things. The chaining of the different cookie jars makes it trivial to create the cookies you want and need.

cookies.permanent.signed[:awesome_cookie] = "cookies awesomeness"
cookies.signed[:awesome_cookie]
 => "cookies awesomeness"

At the time I was working on a project at work that required the use of a lot of dynamic subdomains, and we wanted to be able to write cookies across all of the subdomains as well as for individual subdomains. In Rails 3 you could set the domain when you write to the cookie like:

cookies.signed[:awesome_cookie]     = { :value => "cookies awesomeness",           :domain => "example.com" }
cookies.signed[:awesome_cookie_sub] = { :value => "cookies awesomeness subdomain", :domain => "sub.example.com" }

Now that is a lot of extra work and looks pretty ugly. You could set the default domain for you cookies like this:

Rails.application.config.session_store :cookie_store, :key => '_app_name_session', :domain => :all

But I guess I’d rather explicitly say when a cookie is to be used across all subdomains. To this point I tld-cookies add a tld cookie jar to your Rails 3 app which sets the domain for the cookie to be the root domain, i.e. example.com.

cookies.tld.signed[:tld_cookie] = "ACROSS ALL SUBDOMAINS!!!"
cookies.signed[:tld_cookie]
 => "ACROSS ALL SUBDOMAINS!!!"

As you can see above, you use it similarly to how you would use the permanent cookie jar. The slight difference is when you want to delete the cookie you have to use the tld accessor.

So yeah, first Ruby gem. Poorly named, fun little learning project.

On Selecting A Single Column

July 9, 2011 § 1 Comment

Many times when we are selecting a rows out of the database we just want a single column and have no need for the entire object. There are a number of ways to accomplish this with ActiveRecord. One can get all the records from the database and then collect the attribute needed:

Posts.where(:status => 'published').collect(&:id)
=> [ 1, 5, 8, 10 ]

This has the benefit of being able to us any overwritten accessors, but has a lot of overhead associated with generating the objects. Another way to do it is to go directly to the database:

ActiveRecord::Base.connection.select_values("SELECT id FROM posts WHERE status = 'published'")
 => [ 1, 5, 8, 10 ]

This is much faster, but requires one to use the direct connection to the database and have the SQL literal prepared. Not particularly user friendly even if you can get the SQL literal using the to_sql:

ActiveRecord::Base.connection.select_values(Posts.where(:status => 'published').select(:id).to_sql)
 => [ 1, 5, 8, 10 ]

Wouldn’t it be nicer if you could just do the following:

Post.where(:status => 'published').select_column(:id)
 => [ 1, 5, 8, 10 ]

The select-column gem provides the above functionality above.  You can you it in your Rails 3 app or checkout the source code over on github.

Usage

select_column accepts a single optional argument. This is the column that you want to have returned in an array. The returned column can also be specified using the select query method.

If neither a select nor an argument is given, :id is assumed to be the column to be returned. If multiple select query methods are present, the first one defined will be the column returned.

Some examples:

# selects an array of ids
Post.select_column

# selects an array of titles
Post.select_column(:title)

# selects an array of ids
Post.where(:status => 'published').select_column

# selects an array of titles
Post.where(:status => 'published').select_column(:title)

# selects an array of titles
Post.select(:title).where(:status => 'published').select_column

Update (Jan 21, 2012): It’s like they keep looking at my gems and integrating them into Rails. As of Rails 3.2 this gem’s functionality has been replicated by ActiveRecord::Relation#pluck. Check it out in the release notes.

On Postal Abbreviations

June 30, 2011 § Leave a comment

While working on a Rails form that needed a drop down select box for state postal abbreviations I put together a few Ruby hashes to look things up.  Below are a few snippets from the different hashes so you can see if they work for what you need.  You can check out the full things here.

STATE_ABBR_TO_NAME = {
  'AL' => 'Alabama',
  'AK' => 'Alaska',
  'AS' => 'America Samoa',
  'AZ' => 'Arizona',
  'AR' => 'Arkansas',
  ...
STATE_NAME_TO_ABBR = {
  'Alabama'       => 'AL',
  'Alaska'        => 'AK',
  'America Samoa' => 'AS',
  'Arizona'       => 'AZ',
  'Arkansas'      => 'AR',
  ...
STATE_NAME_TO_ABBR_LOWER = {
  'alabama'       => 'AL',
  'alaska'        => 'AK',
  'america samoa' => 'AS',
  'arizona'       => 'AZ',
  'arkansas'      => 'AR',
  ...

On Rails irb Logging

June 26, 2011 § Leave a comment

When dealing with Rails apps logging debug statements is one of first methods turned to when trying to diagnose a problem.  A lot of times logger.debug is used and will write its output to log/development.log.  This is great when you are running the server, but when you fire up the console and want to work there, you previously had two options. One is to change all of the logger.debug statements into puts so they show up in the console’s terminal window.  The other is two just keep switching back and forth between the console and a tail of the file.

Now you can toggle the app’s logger so that it writes to STDOUT.  Just throw this in your .irbrc file and call toggle_console_logging from the console’s command line.

def toggle_console_logging
  if ActiveRecord::Base.logger == Rails.logger
    l = Logger.new(STDOUT)
    l.level = Rails.logger.level
    set_logger l and return "console"
  else
    set_logger Rails.logger and return "log file"
  end
end

def set_logger(logger)
  ActiveRecord::Base.logger = logger
  ActiveRecord::Base.clear_active_connections!
end

You can check out the gist here.

On Encrypted Cookie Sessions

June 16, 2011 § 1 Comment

Once I had finished up with the encrypted-cookie gem, it seemed like a natural extension to convert it into a Rails 3 session store.  It operates just like the basic cookie session store, just using an encrypted cookie instead of a signed cookie.  It uses the encrypted-cookie gem, so all the encryption is provided by ActiveSupport::MessageEncryptor.  To start using it add the following to your Gemfile:

gem 'encrypted-cookie-store'

And change your session store in config/initializers/session_store.rb

AppName::Application.config.session_store :encrypted_cookie_store, :key => '_app_name_session'

The dependencies will include the encrypted-cookie gem for you.  Accessing the session is the same as always:

session[:tid_bit] = "of information"
session[:tid_bit] # => "of information"

You can check out the source over on github.

Currently this only works with Rails 3.0.*. All of the session code got switched up for Rails 3.1, so it’s going to take some extra work to get it working for the new release of Rails. Update June 18: Got it working with Rails 3.1.  Yay conditional method definitions!!! Sigh…

On Encrypted Cookies

June 16, 2011 § 2 Comments

I love cookies.  Well, despite obviously loving the cookies of the baked goods variety, I also love Rails 3 cookies. I know it’s weird, but if you are looking for things that aren’t weird, you probably are in the wrong place. The specific thing I really like about the Rails 3 cookie system is that it is chainable.  So cool.

They are three types of built-in cookies.  Your basic everyday cookie, a permanent cookie and a signed cookie.  The basic cookie just saves something to a cookie in the user’s browser.  You can add an expiration date if you’d like.  The permanent cookie sets the expiration date 20 years from now and the signed cookie cryptographically signs the contents of the cookie so that people can’t tamper with it.  The way you interact with them is slick.

cookies[:things] = "stuff" # assign
cookies[:things] # read
cookies.permanent[:perm] = "Never going away" # write to a permanent cookie
cookies[:perm] # read a permanent cookie
cookies.signed[:tamper_proof] = "fragile info" # write to a signed cookie
cookies.signed[:tamper_proof] # read a signed cookie
cookies.permanent.signed[:perm_tamper_proof] = # write to a permanent signed cookie
cookies.signed[:perm_tamper_proof] # read the permanent signed cookie

Now it is best practice to not put highly sensitive information in cookies. There is just no telling what is going on with a user’s browser and their computer.  But there are times where you might have some pseudo-sensitive info that you want to store in a cookie.  Not things like social security number, but maybe some app info that you would rather people not see, but it wouldn’t be the end of the world if they did.

Enter encrypted-cookies.

encrytped-cookies is a ruby gem that provides access to encrypted cookies for Rails 3 apps.  It’s built to work exactly like signed cookies, so there isn’t really anything else to say about the usage except to show the two line example:

cookies.encrypted[:secret_cookie] = "nothing to see here" # write to a signed cookie
cookies.encrypted[:secret_cookie] # read a signed cookie

Piece of cake.

I should probably mention how I do the encryption, since that is something you are probably curious about. The great thing about is that I don’t do the encryption!  ActiveSupport has this nifty little module called ActiveSupport::MessageEncryptor.  It does exactly what you think it does.  It encrypts and decrypts messages.  I didn’t want to take a chance writing the encryption code myself.  Waaaaay to many places to make a mistake and really screw over someone using this gem.  The signed cookies in Rails 3 use ActiveSupport::MessageVerifier to make sure the cookie payload hasn’t been messed with.  In reality, encrypted-cookies does little more than swap out verifier code with the encryptor code.  But simple code has fewer places for bugs to pop up.

Happy encrypting.

Where Am I?

You are currently browsing the Ruby category at The On Blog.

%d bloggers like this: