On 3.2

January 30, 2012 § 2 Comments

Last Rails 3.2 post. I swear.

I mentioned ActiveRecord::Relation#pluck in a previous post because it was one I was particularly excited about. After reading through the rest of the release notes I wanted to highlight a few of the other smaller changes I thought were interesting.

ActiveRecord

ActiveRecord::Relation#uniq

Client.select('DISTINCT name')

is now written

Client.select(:name).uniq

You can also revert the uniqueness with

Client.select(:name).uniq.uniq(false)

Don’t user LOWER for case insensitive searches on MySQL

When querying against our databases there are a bunch of times we don’t care about case. In the case of searching against an email address, a case insensitive search does exactly what we want it to. When you add the :case_sensitive => true to a validates_uniqueness_of it use to wrap that part of the query in a LOWER(). This sucked because it prevents MySQL from using an index. When you put this restriction on your email column and have to do this query every time you save a record. Since MySQL does case insensitive searches, ActiveRecord doesn’t worry about adding the LOWER() around the columns. Time saved all around!

rake db:drop drops both dev and test

rake db:create creates both the development and test databases. rake db:drop now drops the both development and test databases instead of just the development database.

ActiveView

namespace form_for

When you have multiple forms on one page for the same type of objects, you can get conflicting ids form elements. Problem solved with form namespaces.

<%= form_for(@offer, :namespace => 'namespace') do |f| %>
  <%= f.label :version, 'Version' %>:
  <%= f.text_field :version %>
<% end %>

The ids of the form elements have the namespace prepended with and underscore to the front of the original id.

ActiveSupport

Time Ranges

One of the things that I love most about Rails is ActiveSupport. And one of the things I love most about ActiveSupport is how it makes dealing with Time awesome and easy. They have added a few new convenience functions to help with date ranges:

  • Time#all_day
  • Time#all_week
  • Time#all_month
  • Time#all_quarter
  • Time#all_year

You can use these with ActiveRecord queries like so:

Event.where(:created_at => Time.now.all_week)
Event.where(:created_at => Time.now.all_day)

Obviously there are a lot of other additions in 3.2, these are just the few that I know that I am going to have a use for. Enjoy.

On Not Rolling Your Own Verification or Encryption

November 10, 2011 § Leave a comment

When developing code, there are somethings that you should leave to the experts. Encryption is one of them. When I wrote my encrypted cookies and encrypted cookie sessions gems, one of the the things I didn’t want to do was write any sort of encryption routines. Luckily ActiveSupport has some help in the way of ActiveSupport::MessageEncryptor. It’s used much like ActiveSupport::MessageVerifier which is used for signed cookies in Rails. People much smarter than me have put these pieces together, so it just makes sense to use them. Almost nothing good can come from trying to do this stuff yourself. Here are some examples of a verifier and encryptor being used.

ActiveSupport::MessageVerifier

> secret = ActiveSupport::SecureRandom.hex(10)
 => "379af645b8dcce20b607"

> verifier = ActiveSupport::MessageVerifier.new(secret)
 => #<ActiveSupport::MessageVerifier:0x00000103d7ce50 @secret="379af645b8dcce20b607", @digest="SHA1">

> signed_message = verifier.generate("sign this!")
 => "BAhJIg9zaWduIHRoaXMhBjoGRVQ=--af1e810b074b1abd6d9dcd775f71b1fafa53c218"
# this is "<base 64 encoded and serialized string>--<digest of string>"

> verifier.verify(signed_message)
 => "sign this!"

> verifier.verify(signed_message + "alittleextraontheend")
ActiveSupport::MessageVerifier::InvalidSignature

> verifier.verify("alittleextraatthebeginning" + signed_message)
ActiveSupport::MessageVerifier::InvalidSignature

ActiveSupport::MessgeEncryptor

> secret = ActiveSupport::SecureRandom.hex(20)
 => "c1578de6ec2e1789940729dc9d97b335fc7df588"

> encryptor = ActiveSupport::MessageEncryptor.new(secret)
 => #<ActiveSupport::MessageEncryptor:0x000001295337c8 @secret="c1578de6ec2e1789940729dc9d97b335fc7df588", @cipher="aes-256-cbc">

> encrypted_message = encryptor.encrypt_and_sign("Nothing to see here...")
 => "BAhJIl9YbDZkK0czS3o0ZkI0Yml6K05uYzgzM05meDJjWWU4QWh0YzdFeFFrbC85b3BocHFORWtRWXdDVWIxaW45TEQ5LS1yVkxGTURJYzFWb2pva0UrVkkwTkFnPT0GOgZFRg==--e61e02a818960d66c7865f5624fad63b1564283f"

> encryptor.decrypt_and_verify(encrypted_message)
 => "Nothing to see here..."

If your secret is too short you’ll get a OpenSSL::Cipher::CipherError. Make sure your secret is at least 32 bytes. Using ActiveSupport::SecureRandom.hex(16) should satisfy this requirement, but obviously longer is better. You can also pass in a :digest => <digest> option as a second argument to both initializers to specify a different algorithm to use.

One of my thoughts is to submit these two gems into Rails so that people don’t make the mistake of trying to roll their own encryption systems for cookies. We’ll see how it goes.

On the Differences of Months

July 28, 2011 § Leave a comment

Dealing with dates is hard.  Dealing date calculations is harder.  Luckily, in the Rails world we have ActiveSupport to help us with a lot of this.  It actually does so much, that I usually forget how much of a pain dealing with dates is suppose to be.  However, there are times now and then that I am reminded.  Little edge cases that haven’t yet been built into ActiveSupport.  One such edge case is determining the number of months between two dates. Why do months have to have different numbers of days?

This particular use case can be very useful when dealing with recurring payments.  Calculating the number of payment cycles a subscriber has gone through can tell you how much revenue they have generated.  I have put together two class methods for the Time object that will calculate just this.  One is a simple loop that takes time proportional to the time between the start and end times, and the other a more efficient direct calculation of the same number.

The simple loop looks like:

def months_between2(start_date, end_date)
  return -months_between2(end_date, start_date) if end_date < start_date

  count = 1

  while true
    return count - 1 if (start_date + count.months) > end_date
    count += 1
  end
end

It just starts at the start_date and keeps adding months until it passes the end_date. Not particularly difficult, but should get the job done for most cases.  All the complexity of the more efficient version comes from checks dealing with the various cases arising from different months having a different number of days. Anyways, the source code for that one looks much nicer over on GitHub.

Where Am I?

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