MySQL gem on OSX

Posted by Brian in Rails, Howto, snacks (June 1st, 2007)

When I got the Macbook Pro in March, I immediately installed Ruby, Rails, Subversion, etc, using this tutorial from Hivelogic. http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx
They’ve done a good job of keeping it up to date, but one problem I noticed is that the MySQL gem instructions don’t work anymore. When the article was first written, it contained steps to remedy a minor installation problem. However, enough users reported that the problem no longer existed so it was removed.

Today, I encountered that problem again, and thought I’d share the solution. If you’re getting an error when you do

  asudo gem install mysql -- --with-mysql-dir=/usr/local/mysql

You’ll need to do the following to fix it.

First, look at where the gem says it’s left its files.

  Gem files will remain installed in /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7 for inspection.

So in my case, I’ll do

  cd /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7

Then I’ll edit the file ‘mysql.c’ in that folder and add one line at the top of the file:

  #define ulong unsigned long

Then, I just run

  sudo make
  sudo make install

The original fix for this comes from
http://jlaine.net/2006/10/3/installing-ruby-mysql-driver-on-os-x but the instructions there are a little backwards. I hope this solution will help others having a similar problem.

Updating Multiple Fields in Rails

Posted by Brian in Rails, snacks (April 23rd, 2007)

Just a quick one today…. I always have to look this one up.

Let’s say you have a guestbook and you want to approve multiple entries quickly. Rails has a built-in method for handling that, although it’s poorly documented.

Assuming your model “Entry” has an “approved” attribute that maps to a boolean field in the database, you might make a form like this:


<% form_tag :action=>"approve" do %>
  
  <% @entries.each do |@entry| %>
     <p>Posted at<%=@entry.created_at %> by
     <%=@entry.email %> </p>
 
     <p><%=h(@entry.body) %></p>
     <p>Approved? <%=check_box "@entry[]", "approved" %></p>
  <% end %>
  
  <%= submit_tag "Moderate Entries" %>
<% end %>

The key here is that you’re iterating over the items in the collection and using an instance variable to hold each one (@entry). Normally, a check_box helper looks like this:


   <%=check_box "entry", "approved" %>
 

If you use the check_box helper with an array as the first parameter, you can create an array of entries that can all be sent to the back-end.


  <%=check_box "entry[]", "approved" %>

Rails puts the primary key of the entry inside of the square-brackets, thus causing them to group nicely.

Your params hash will look like this:


{
"commit"=>"Moderate Selected Entries",
"action"=>"moderate",
  "entry"=>{
     "1"=>{"approved"=>"1"},
     "2"=>{"approved"=>"1"}
  },
  "controller"=>"guestbook"
}

That’s the hard part. The action that receives the POST request looks like this


  def moderate
     Entry.update(params[:entry].keys, params[:entry].values)
     flash[:notice] = "Successfully modified the approval flags for all records."
     redirect_to :action=>"index"
  end
 

Yeah, you read that right… no looping. The “update” class method on an ActiveRecord object can take either an ID or an array of IDs as the first parameter, and a hash of names and values or an array of hashes containing names and values for the second parameter. Since the form made the record ids as the keys in the hash, calling params[:entry].keys gives us the array of IDs we need for the first parameter. params[:entry].values gives us an array of the hashes we need for the second parameter.

Now, you can use this technique to do some simple mass-update pages if you needed to, but then it gets a little trickier.

I hope someone out there finds this useful.

Unit testing

Posted by Brian in Rails, snacks (April 3rd, 2007)

Rails developers typically do test-driven development, so they’re writing their unit tests before they write their business rules. For example, if I was to write a keyword search method, I’d write a unit test first.


 
  # Should find 2 because I have “red” in the test data in 2 records
  assert_equal(2, Document.find_by_keywords(“red”)  
 
  # should find 0 because that term isn’t in any of the records
  assert_equal(2, Document.find_by_keywords(“blue”)
 
  # should find 2 because the word “ninja” is found on one document
  # and ‘pirate’ is found in another.
  assert_equal(2, Document.find_by_keywords(“ninja pirate”)
 

The test that I wrote gives me a clear indication of the requirement I have to implement. When these tests (and more) all pass, I know that I have my search engine finished. I don’t need to continously try various examples manually to see if I am done. It takes a lot of the manual testing away, but it also gives me more confidence that my code does what I think it does.

The hardest part about testing is learning to do it first. It takes much longer to write tests after you already have a working application, and it feels pointless. (It’s really not, but it just feels that way when you’re doing it which can be really demotivating.)

Associations and caching

Posted by Brian in Rails, snacks (April 2nd, 2007)

Sometimes I just do dumb things when I write code. The way I look at it, every mistake I make is a chance to learn something new.

I’ve got a system with workshops, presenters, enrollments, and more. It’s a fairly complex system that has a lot of relationships. If you peek into my User model, you’ll see some things like this:


  class User < ActiveRecord::Base
    has_many :presenterships
    has_many :troubleshooterships
    has_many :enrollments  
    has_many :presentations, :through=>:presenterships, :source=>:workshop
    has_many :troubleshoots, :through=>:troubleshooterships, :source=>:workshop
    has_many :workshops, :through=>:enrollments
  end
 
  .…
  end

This post Turn finders into associations and get caching for free got me thinking about my code a bit so I decided to take a second look. When I did, I saw this method:


  #Returns all of the workshops for which this user is enrolled.
  def workshops_enrolled
    @workshops = Workshop.find :all, :include=>[:offering, :enrollments],
        :conditions=>["enrollments.user_id = ? and enrollments.withdrew = ?", self.id, false]
  end

I added this method because I wanted to make sure I only showed associations where the user did not withdraw. (I’m recording withdrawls from a workshop instead of removing the association between the user and the workshop.)

Darn it… that’s just not right. While that works, I lose the ability to cache, and it’s also exactly what an association is for. Refactored, it now looks like this:


    has_many :workshops_enrolled, :source=>:workshop, :include=>[:offering, :enrollments],
           :through => :enrollments, :conditions=>["enrollments.withdrew = ?", false]
 

Since it’s a has_many :through, it needs to have the :source option defined so Rails knows what the parent class is.

Fun stuff, this Rails.

Upgrading Ruby to 1.8.6 on OSX and Windows

Posted by Brian in Rails, snacks (March 24th, 2007)

Mac users

Assuming you’ve followed the Hivelogic article that walks you through a basic Ruby on Rails installation, you can use this code snippet to install Ruby 1.8.6.


cd /usr/local/src
curl -O  ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz
tar -zxvf ruby-1.8.6.tar.gz
cd ruby-1.8.6
./configure --prefix=/usr/local --enable-pthread --with-readline-dir=/usr/local
make
sudo make install
sudo make install-doc
cd ..
sudo gem install rails --include-dependencies

Windows users

Windows users only need to snag the latest version of Curt Hibbs’ One Click Ruby Installer which can be downloaded here.

The major advantage of moving to Ruby 1.8.6 and Rails 1.2.3 is that breakpointer works again.

Update: When I did this on my Macbook Pro, I had to recompile a few libraries like Ruby-DBI and Ruby-ODBC before I could connect to Microsoft SQL Server again.

Serializing ActiveRecord objects

Posted by Brian in Rails, snacks (March 19th, 2007)

One neat feature of Rails’ ActiveRecord objects is the #serialize method which will allow you to store an object in the database as YAML. This means, for example, you could have a User and a Profile. The Profile could be a hash containing a bunch of values that you want to manage, but that don’t necessarily need their own database table.


  class User < ActiveRecord::Base
  
    serialize :profile
  end
 
  u = User.find 1
  u.profile = {"url" => "http://www.rubyonrails.com", :nickname =>"Ninja master"}
  u.save
 
  u = User.find 1
  u.profile.class
  => Hash
 

The magic of the #serialize method takes the given object, serializes it to YAML, stores the YAML in the database, and then unserializes it when you retrieve the data.

This is all well and good, and I thought I would take advantage of this to help me easily cache some data. I have a system in which I have tasks, and a task belongs to a Service which contains the rate we charge for the task. Now, I really want to be able to store the service name and rate on the task when it’s assigned so that the task won’t be affected when I change my rates in the future.

“Aha!” I thought,”I can just use serialize and store the Service object right on the task!” I created a migration that added a service_data field to my database


./script/generate migration AddServiceDataToTasks


 
class AddServiceDataToTasks< ActiveRecord::Migration
  def self.up
    add_column :tasks, :service_data, :text
  end
 
  def self.down
    remove_column :tasks, :service_data
  end
end
 

I wrote a quick unit test which I knew would come in handy later.


   def test_saves_service_when_task_is_created
      @service = Service.find_by_name "Rails development"
      task = Task.create :name=>"Create user registration site", :esthours => 5, :service => @service
      t = Task.find task.id
      assert_not_nil t.service_data
      assert_kind_of Service, t.service_data
  
   end

Then I modified my Task model


  class Task < ActiveRecord::Base
    belongs_to :service
    serialize :service_data
 
    after_create  :sync_service_data!
  
    def sync_service_data!
       self.service_data = self.service
       self.save!
    end
  end

That seemed simple enough. However, when I tried it, I got a nasty surprise…


 
  t = Task.find 1
  t.service_data
  => nil
 

No matter what i tried, the service data always came back empty.

Running my unit test proved that something was definitely wrong, as I kept seeing “nil expected to not be nil”.

After searching and playing, I decided that #serialize was just not capable of serializing ActiveRecord objects. To get around this, I simply changed my code slightly. I knew that #serialize can handle Hashes so I stored just the attributes hash. Then I redefined #service_data “getter” method to create a new instance of Service from that hash.


  class Task < ActiveRecord::Base
    belongs_to :service
    serialize :service_data
 
    def sync_service_data!
       self.service_data = self.service.attributes
       self.save!
    end
 
    def service_data
      Service.new(self.attributes["service_data"]
    end
end

A quick run of the tests showed that I was now getting what I wanted.

Shortly after I discovered this solution, Jon Garvin offered a much cleaner solution…. don’t use Serialize. He found that Serialize does some strange magical things that often get in the way of our intended results. He proposed that I try

  class Task < ActiveRecord::Base
    belongs_to :service
 
    after_create  :sync_service_data!
  
    def sync_service_data!
       self.service_data = self.service
       self.save!
    end
 
    def service_data
     self[:service_data] ? Marshal.load(self[:service_data]) : nil
    end
 
    def service_data=(service)
      self[:service_data] = Marshal.dump(service)
    end
 
  end

This method simply creates a setter that manually marshals the data to the database column, and a getter that retrieves it again. This method works great, and I thank Jon for his quick solution!

Test-driven development - Simple example

Posted by Brian in Rails, snacks (March 9th, 2007)

This is a really simple example of why unit tests are important. I was just working with a project where I am creating workshops. Students enroll in these workshops and I have a method on the Workshop model that handles the enrollment.

(more…)

Connecting to Microsoft SQL Server from Mac OS X

Posted by Brian in Rails, snacks (March 8th, 2007)

Updated on July 2nd, 2008.

As you may know, I develop a lot of new web applications using Ruby on Rails, and to completely buy in to the whole “cult that is Rails”, I purchased a Macbook Pro for development. I am really happy with it, but one of the problems I ran across is the fact that there’s no built-in or prepackaged way to connect to Microsoft SQL Server which I use at work for a lot of my projects.

This guide is written from my notes and should give you the tools you need to get up and running. Some of this information comes from the Ruby on Rails wiki, but some things there are a bit misleading.

This now works on Leopard too!

(more…)

Excel Export, the Stupid Simple Way

Posted by Brian in Rails, snacks (January 24th, 2007)

It occurred to me the other night while helping Mike figure out how to prepare some reports in Excel from a Rails application that it probably wasn’t necessary to use the plugin I created. One of the big drawbacks to the Excel Export plugin is that it doesn’t currently support formatting of cells. (There just really isn’t a good way to do that.) One trick I’ve known about for years is the ability for Excel to read in an HTML table of data and render it as a spreadsheet.

This meant that we could simple create a new view that contained the HTML table

 
<table border="1" id="doc">
  <tr>
    <td>Last Name</td>
    <td>First Name</td>
    <td>Birthday</td>
    <td>Email</td>
  </tr>
  <tr>
    <td><%=user.last_name %></td>
    <td><%=user.first_name %></td>
    <td><%=user.birthday %></td>
    <td><%=user.email %></td>
  </tr>
</table>
 

Then it’s just a simple matter of making a controller action to display that view.

  def export
    headers[’Content-Type’] = “application/vnd.ms-excel”
    render :layout=> false
  end

That should cause the browser to open Excel, instead of displaying the page. Now, I can’t guarantee that this will work on Linux, OSX, or anywhere where Excel is not installed. However it certainly meets our needs for this one project and so I t hought I would share it with everyone else.

« Previous Page