Posts Tagged Rails

Patching Formtastic to append HTML to input list elements

Formtastic is pretty killer when it comes to creating nice looking forms very quickly. I love the short syntax compared to the default Rails form helpers, but sometimes I find the black box helpers a bit restricting when it comes to customizing the forms. Usually modifying the CSS is enough to get desired results, but I ran into a case today where it would have been an ugly hack to make it work with CSS.

Problem

I need to add HTML inside the <li> tag that is produced by the formtastic input helper. The helper produces HTML that looks like this:

I want to insert a link in betweent the input field and the inline hints. This is not possible, since the formtastic input helper outputs this entire HTML snippet. You can customize plenty of things within the helper, but I did not see a way to add more html content.

Solution

As I started to dig through the formtastic source I noticed that all of the input elements are using a wrapper module that produces the content inside the <li> tag. Take a look at this module:

All of the Formtastic input classes (StringInput, TextInput, HiddenInput,etc..) pass a block to the input_wrapping method inside their to_html method. The content_tag method takes the element name, content and element options as arguments. See here for more info: http://apidock.com/rails/ActionView/Helpers/TagHelper/content_tag
In order to add my content, I will need to add a String element (my HTML) to this array:

As you can see in the code above, it is just an array of strings that are joined together by carriage returns and marked as safe using the Rails String core_ext html_safe. So I will have to insert my HTML code in the desired position of this array. In my scenario, it makes sense to put the extra HTML between the input tag and the error html. I decided to monkey patch this method (oops, maybe dangerous if I ever upgrade formtastic for this app). I added this code to /app/config/initializers/formtastic_monkey_patch.rb:

The first thing I needed to patch was the Html module. 3 methods were added to this module to allow me to pass :extra_html and :extra_html_class to the formtastic input helper. This was pretty much a copy/paste from “Formtastic::Inputs::Base::Hints”. Then I just had to add the “extra_html” to the array inside the “input_wrapping” method. Now I can do this in my form:

This will output:

I can take this a step further by adding another option for the order that the extra html will appear inside the <li> tag. New monkey patch:

I added one method inside the Html class to set a default extra_html_position within the options hash. Then I modified “input wrapping” so it inserts the extra_html into the specified position. Here is the new formtastic input call in my view:

By passing the :extra_html_position option with a value of zero, formtastic will now insert the extra_html before the input tag. So there ya go… monkey-patched and ready to roll.

Tags: ,

Developing is_able or acts_as plugins for Rails 3 – Part 1

Note: This is an introduction explaining the concept of “acts_as”. This will not go into detail about creating the plugin.. only the basic description of the functionality of the plugin. Part 2 will go into detail about creating the structure outside of  this and making it a real plugin.


You have seen them everywhere in the Rails world. Acts as this, Acts as that, Is able…. Most of these plugins have the same goal: Allow any ActiveRecord subclass to “have many” of another specific AR subclass. For example, acts_as_taggable_on allows a post, article, product, or any other AR model to have many tags. Hence the name “taggable”. These plugins take care of the associations for you, by adding only one extra database table to your schema. You don’t have to alter the “able” model’s table structure to make it work. “Able” plugins do this by using polymorphism. It is actually quite simple to create something that is “able” using Rails 3. In this tutorial, I will create the code directly in the Rails app, instead of using a gem (so as the introductory note states… this is not a real plugin yet). Check it out:

Requirement:

Create a plugin that allows any ActiveRecord model to have many reviews.  Reviews will have a rating, a text review, pros, cons, and a user ID

Implementation:

1. Create the review Model

Easy enough to create using Rails generators:

rails g model review rating:integer review_text:text pros:string cons:string user_id:integer reviewable_type

2. Define the polymorphic associations in the model and migration

You will need to add this line to your migration in order to generate the 2 extra columns for polymorphism (reviewable_type,reviewable_id)

t.references :reviewable, :polymorphic => true

This will spit out our migration and create the Review model.

Next we will add the polymorphic association to the Review class that was just generated:

class Review < ActiveRecord::Base
   belongs_to :reviewable, :polymorphic=>true
end

Basically, this tells Rails that you want to the Review to belong to “reviewable” and “reviewable” can be any other type of model.  Behind the scenes, Rails just looks at the “reviews” table for “reviewable” using the “reviewable_type” and “reviewable_id”.  This may be a little confusing if you are not familiar with polymorphic associations in Rails.  Lets take a look at how the records actually show up in the table and it may make a little more sense:

id | reviewable_type  | reviewable_id | review_text |
1  | "Product" | 99 | "my review of product #99" |
2  | "Article" | 25 | "my review of article #25" |

As you can see, each row represents a single review, but each review belongs to a different model instance. Review #1 belongs to Product #99, and review #2 belongs to Article #25. By creating the 2 “reviewable” columns, you are able to associate a review with more than one model class.

3. Run the migration

rake db:migrate

4. Create a module that ActiveRecord::Base will extend

This allows you to keep things DRY and simple in each model that is “reviewable” (has_many :reviews). You can drop this into your /app/models directory.

module Reviewable
  def is_reviewable
    has_many :reviews, :as=>:reviewable, :dependent=>:destroy
    include InstanceMethods
  end
  module InstanceMethods
    def reviewable?
      true
    end
  end
end
ActiveRecord::Base.extend Reviewable

First, lets look at the last line in the code above. When this Ruby file is evaluated it will make ActiveRecord::Base extend Reviewable. Basically when you extend a module, the class methods are added to the class that is extending the module. So ActiveRecord::Base now has a class method named “is_reviewable”. Since all your models extend ActiveRecord::Base, they will also respond to this class method.

Next take a look inside the class method. The “is_reviewable” method calls the “has_many” class method and also adds an instance method to the ActiveRecord::Base subclass instance. Lets take a look at a real example that would use the “is_reviewable” class method.

class Product < ActiveRecord::Base
   is_reviewable
end

This used to look like some kind of magic to me when I was a Rails novice. Its just a nice DSL, but behind the scenes this line is calling a class method. Since this line is not in a “def”, the class method will be executed immediately when the class is loaded by Rails. in other words… self.is_reviewable. The class is loaded, the method is executed and has_many (also a class method) is executed and the methods defined in the InstanceMethods module are included in the Product class.

5. Try it out

For simplicity I will not include the tests here, but please note that it is important to create tests using Rspec (or whatever framework you prefer) for all of this code.  These console lines could easily be translated into a test spec.

Lets see this in action in the Rails console:

#check to see if the class method is there for AR Base
ActiveRecord::Base.methods.include?(:is_reviewable) => true

#check to see if class method is there for Product (has to be b/c it subclasses AR Base)
Product.methods.include?(:is_reviewable)
=> true

#check to see if the instance method is there
Product.instance_methods.include?(:reviewable?)
=> true
product = Product.new
=> #

product.reviewable?
=> true

product.reviews
=> [] #nothing here yet.. we havent created any

product.reviews.create(review_text: "test")
=>  #this results in error.. you can create a review until the product has been saved

product.save
product.reviews.create(review_text: "test")
=> #<Review id: 1, reviewable_type: "Product", reviewable_id: 101 ....>

product.reviews.size
=> 1

Conclusion

It looks a bit complicated and magical when you first encounter these plugins, but in reality its as simple as adding a class method to AR. Please read this blog by Yehuda Katz which shows how some people make the extends/includes thing alot more complicated than it should be. I was using the “overkill” way before I read his post. It totally makes sense and removes some of the confusion you may have when looking at the source of some of the “able” plugins.

Tags: , , , ,

Gem dev tutorial: Adding Rails 3 Engines to Jeweler

If you are a Ruby or Rails developer, chances are you will run into a situation where you want to modify a gem that is on github.  In this tutorial I will show you a real-world example of how I do this, using Jeweler as an example.

I like Jeweler.. I like it alot.. but I always end up creating a gem with it and then tweaking a bunch of files to turn it into a Rails 3 Engine. So I am attempting to integrate Rails 3 engines into Jeweler by adding a command line argument that will tell jeweler to create the engine directories and add the engine file templates.

Fork The Repo

First, I need to fork the repo so I can add my changes. I just login to my github account and go to the jeweler page and click the fork button. Now I have a repo under my account. Then I clone the repo on my dev box. In order to pull changes from the original repo when anything changes, I will need to add a remote upstream:

#clone my forked repo
git clone git@github.com:johnmcaliley/jeweler.git
#add upstream that points to original repo
git remote add upstream git://github.com/technicalpickles/jeweler.git

Test Driven Development

I see that this project uses Cucumber for many of its tests. This time I decide to be a good boy and do some TDD from the get-go. The first thing I want to do is create a cuc feature and write 2 scenarios. The first is a scenario without the –rails3_engine argument. The second is with the argument. I used some of the steps from the cucumber.feature in order to get up and running.

Feature: generating rails3 engine directories and files
In order to use rails 3 engines with jeweler generators
A user should be able to
generate a project setup and specify the rails3_engine option

Scenario: sans rails3 engine setup
Given a working directory
And I have configured git sanely
And I do not want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is not created
And a file named ‘the-perfect-gem/app/controllers’ is not created
And a file named ‘the-perfect-gem/app/helpers’ is not created
And a file named ‘the-perfect-gem/app/models’ is not created
And a file named ‘the-perfect-gem/app/views’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is not created
And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is not created

Scenario: Rails 3 engine setup
Given a working directory
And I have configured git sanely
And I want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is created
And a file named ‘the-perfect-gem/app/controllers’ is created
And a file named ‘the-perfect-gem/app/helpers’ is created
And a file named ‘the-perfect-gem/app/models’ is created
And a file named ‘the-perfect-gem/app/views’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is created

And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is blank

And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘the-perfect-gem’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘rails’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has a module based on the class name
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ subclasses rails engine
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has placeholders for initializers and rake task inclusion

These scenarios have some steps that are not defined, so obviously they will fail. I need to add the missing step definitions.

##features/step_definitions/generator_steps.rb

Given /^I do not want to create a rails3 engine$/ do
  @rails3_engine = false
end

Given /^I want to create a rails3 engine$/ do
  @rails3_engine = true
end

# one line added here also:
When /^I generate a (.*)project named '((?:\w|-|_)+)' that is '([^']*)' and described as '([^']*)'$
#....
  @use_cucumber ? '--cucumber' : nil,
  @rails3_engine ? '--rails3_engine' : nil, # I added this
#....
end

Then /^'(.*)' has a module based on the class name$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match %Q{module ThePerfectGem}, content
end

Then /^'(.*)' is blank$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "", content
end

Then /^'(.*)' subclasses rails engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "class Engine < Rails::Engine", content
end

Then /^'(.*)' has placeholders for initializers and rake task inclusion$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "#rake_tasks do", content
  assert_match '#load "the-perfect-gem/railties/tasks.rake"', content
  assert_match "#initializer 'the-perfect-gem.helper' do |app|", content
  assert_match "#ActionView::Base.send :include, ThePerfectGemHelper", content
end

Then /^'(.*)' requires the engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match 'PATH = File.dirname(__FILE__) + "/the-perfect-gem"', content
  assert_match 'require "#{PATH}/engine.rb"', content
end

Dig Into The Existing Code

Next I need to figure out how this thing works and add my code.

The gem contains a binary file under /bin that includes the jeweler libraries and then fires off a generator that accepts arguments

require 'jeweler/generator'
exit Jeweler::Generator::Application.run!(*ARGV)

The run class method is in lib/generator/application.rb. It does some checks on the options and then creates an instance of Jeweler::Generator and calls its run method

generator = Jeweler::Generator.new(options)
generator.run

Make changes to generator.rb

Taking a closer look at the initialize method, I see that I will need to add something for the “rails3_engine” argument that I pass to the jeweler binary. I will add something similar to the cucumber option

self.should_use_cucumber    = options[:use_cucumber]
self.should_create_rails3_engine = options[:rails3_engine] # added this code

I also need to add an attribute accessor so the object can set this instance variable:

attr_accessor   # a bunch of existing attr_accessors listed here.. append mine to end of list
,:should_create_rails3_engine

I think that will do for the initialize stuff. Next I look at the run instance method. It has a method called create_files. I will need to add a condition to this method so Jeweler can check for my rails3_engine option and create the appropriate files. Again I look at the cucumber option for inspiration, since it creates directories in the gem and adds files.  Similar to the cucumber condition, the “rails3_engine” option will create some directories and output my engine template files into these directories.

if should_use_cucumber
  mkdir_in_target           features_dir
  output_template_in_target File.join(%w(features default.feature)), File.join('features', feature_filename)

  mkdir_in_target           features_support_dir
  output_template_in_target File.join(features_support_dir, 'env.rb')

  mkdir_in_target           features_steps_dir
  touch_in_target           File.join(features_steps_dir, steps_filename)
end

# here is what I added
if should_create_rails3_engine
  mkdir_in_target           app_dir
  mkdir_in_target           controllers_dir
  mkdir_in_target           models_dir
  mkdir_in_target           helpers_dir
  mkdir_in_target           views_dir
  mkdir_in_target           main_dir
  output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')
  mkdir_in_target           generators_dir
  mkdir_in_target           railties_dir
  touch_in_target           File.join(railties_dir,'tasks.rake')
  output_template_in_target File.join('rails3_engine','lib_engine_template.erb'), File.join(lib_dir,lib_filename)
end

I also need to add instance variables for the directories that are used in the rails3_engine option condition. Again, I look at what was coded for cucumber:

#check for a dash in the project name and convert to underscore so classify understands it
def project_class
  self.project_name.gsub("-","_").classify
end

def app_dir
  'app'
end

def controllers_dir
  "#{app_dir}/controllers"
end

def models_dir
  "#{app_dir}/models"
end

def views_dir
  "#{app_dir}/views"
end

def helpers_dir
  "#{app_dir}/helpers"
end

def main_dir
  "#{lib_dir}/#{self.project_name}"
end

def generators_dir
  "#{lib_dir}/generators"
end

def railties_dir
  "#{main_dir}/railties"
end

Since I use the pluralize method in “project_class” I need to include active_support inflectors in the Jeweler class

require 'git'
require 'erb'
require 'active_support/inflector' # I added this one
require 'net/http'
require 'uri'

Add code to options.rb

One part that I notice is missing while looking at how cucumber is set up is the code in lib/jeweler/generator/options.rb.

o.on('--cucumber', 'generate cucumber stories in addition to the other tests') do
  self[:use_cucumber] = true
end

# I added this
o.on('--rails3_engine', 'generate rails3 engine directories and files') do
  self[:rails3_engine] = true
end

Add the file templates

generator.rb makes a few calls with methods like this:

output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')

I created a directory called “lib/jeweler/templates/rails3_engine” that will house my template files. Then I add 2 erb templates that will generate the source code files dynamically in the gem.
Here are the files:

##engine_template.erb
require "<%=require_name%>"
require "rails"

module <%=project_class%>
  class Engine < Rails::Engine
      #rake_tasks do
        #load "<%=require_name%>/railties/tasks.rake"
      #end

      #initializer '<%=require_name%>.helper' do |app|
        #ActionView::Base.send :include, <%=project_class%>Helper
      #end
  end
end
##lib_engine_template.erb
PATH = File.dirname(__FILE__) + "/<%=require_name%>"
require "#{PATH}/engine.rb"

The first file creates the engine.rb which is necessary to make our gem behave as a Rails 3 engine plugin. Using erb allows me to insert text dynamically based on the project name and the require directory which were set in generator.rb instance methods. The second file is just a simple template that requires engine.rb in the project.

Fire off them tests man!

This tutorial does not reflect what I actually did in development.  I run my tests first, look at the failures, write code, refactor, then repeat until everything is green as a cuc.  But to simplify, I am just showing that you need to run tests after you write your code.  Jeweler uses a rake task to execute all the cuc scenarios, so I run this command:

rake features
103 scenarios (103 passed)
652 steps (652 passed)

Now I can see all of my scenarios as well as existing scenarios are all passing. Good to go!

I also updated the unit test, test_options.rb.  But I am getting some errors relating to ‘rr’ -> “NotImplementedError: super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later”.  I don’t think I will be able to run these tests until I get this resolved.  Looks like it is an issue with ‘rr’ and ruby 1.9.2.  I might try downgrading ruby using RVM and running from there.

Test with a live app

After the cucumber tests are passing, I want to look at this in a real Rails 3 app.  So I create a new gem using my jeweler fork. I just execute the jeweler bin from my fork:

>cd jeweler
>bin/jeweler myengine --rails3_engine
create  .gitignore
create  Rakefile
create  Gemfile
create  LICENSE.txt
create  README.rdoc
create  .document
create  lib
create  lib/myengine.rb
create  test
create  test/helper.rb
create  test/test_myengine.rb
create  app
create  app/controllers
create  app/models
create  app/helpers
create  app/views
create  lib/myengine
create  lib/myengine/engine.rb
create  lib/generators
create  lib/myengine/railties
create  lib/myengine/railties/tasks.rake
create  lib/myengine.rb
Jeweler has prepared your gem in myengine

I add a controller and a view to the gem, create a gemspec and install my gem. Then I add the gem to a rails 3 app Gemfile (using :path=>/path/to/my/gem), bundle install, fire up the app server and take a look at the view that I added.

It appears, so I know that the Engine was initialized in the app. Good to go!

Here is the fork if you want to look at the code:
https://github.com/johnmcaliley/jeweler

Tags: , , , , , ,

Setting up a shared image upload directory in Rails production environment with Capistrano

Every time you deploy with Capistrano you get a new release directory that is linked to:

/your_rails_apps_dir/your_rails_app/current

If you have images or other files that are being uploaded in your app, then you want to put these files outside of the rails directory structure. If you allow users to upload images directly to /your_rails_app/public/images, then these files will not be visible after a deploy because the current directory will be linked to a freshly installed version of your rails app which does not contain these files. The solution is fairly straightforward and is similar to the way Capistrano links your latest release to the “current” directory. Here is what you need to do:

1. Create a new directory for your images in the shared directory of your production app.

mkdir /your_rails_apps_dir/your_rails_app/shared/images
mkdir /your_rails_apps_dir/your_rails_app/shared/images/uploads

Note: Make sure you set the correct permissions so your app server can read/write these directories

2. Create a link from to these directories in your cap deploy script.

You can create a new task that runs after the “current” directory symlink. Capistrano already has “shared_path” and “release_path” variables for you to work with:

#config/deploy.rb
task :after_symlink do
  run "ln -nfs #{shared_path}/images/uploads #{release_path}/public/images/uploads"
end

3. Change your file upload code so that it writes to:

/your_rails_apps_dir/your_rails_app/shared/images/uploads

4. Change your view code so it uses the image upload path

This would be the same as it would if you had the images in your public images directory

/images/uploads

5. Deploy your app

cap deploy

Now when you deploy with Capistrano, it will automatically link the uploads directory to your current release and your users can access these files with your rails app. The links make it appear that the files are in /current/public/images/upload

Tags: , ,

Rails 3 Upgrade.. my experience

UPDATE 1 (6/29/2010): Today I purchased a copy of “Rails 3 Upgrade Handbook” by Jeremey McAnally, who wrote the rails_upgrade plugin.  I would highly recommend reading this book as your starting point for Rails 3 upgrade, as I wish I read this before I started my upgrade.  He goes into alot of detail about what you need to change.  The steps differ some from what I wrote in this post, and you might find it easier to follow his steps.

This is going to be quite a long post and I will probably be updating it daily for the next few weeks.  Now that Rails 3 RC is right around the corner, I figured it would be a good time to upgrade CouponShack.com.  I have been meaning to refactor my code for quite a while and clean up the mess I have created in the last 3 years.   I am finally getting around to it and I thought upgrading to Rails 3 would be a good start, so here is my experience upgrading to Rails 3, from Rails 2.3.2.  I am writing some of this after the fact, so hopefully I didn’t leave out any steps:

Current stable setup on CouponShack.com

  • Co-location hosting in my city
  • CentOS 5
  • nginx
  • Mongrel 1.1.5
  • Rails 2.3.2
  • Ruby 1.8.7

Proposed upgrade

  • Rackspace Cloud Servers hosting
  • Rails 3 beta4 (upgrade to RC when ready)
  • Ruby 1.9.2dev
  • New app server… looking at Passenger right now

The upgrade list may change depending on how everything pans out.  I was planning on moving over to Rackspace Cloud (where most of my other apps are currently), so I figured I would go with Rails 3 as a part of that move.

Note: I use Mac OSX 10.5.8 and Textmate for development

Another Note: A good place to start is to read the Rails 3 Release Notes.  This document explains the new features, upgrade path, and has alot of good links to blog posts by the rails developers that relate to the new features.  My blog post reiterates alot of this info while showing you how it relates to my specific upgrade.  I recommend reading as much as you can about Rails 3 before you get your feet wet with the upgrade… I know that helped me.

Upgrade Steps

Step 1 – Install RVM

RVM will allow you to run multiple versions of Ruby on your development box.  This is a must if you are upgrading to Rails 3 and still want to use Rails 2.  Follow the instructions on the RVM website and install it from github.

Step 2 – Install Ruby 1.9.2

After RVM is installed, you will want to install Ruby 1.9.2 using RVM.

rvm install 1.9.2

It will take a few minutes.  On my system it installed ruby-1.9.2-preview3. After its done you can switch to 1.9.2 by using this command.

rvm 1.9.2

Now your Mac is using 1.9.2 instead of the system ruby (you can switch back with “rvm system” if you need to code some rails 2 stuff again)

Step 3 – Install Rails 3

Using Ruby 1.9.2 on your system, you can now install Rails 3.  One thing to note here is that you now have a clean slate for all your gems.  RVM conveniently stores your gems in a directory under your account ~/.rvm/gems/ruby-1.9.2-preview3/gems.  This will allow you to keep your Rails 2 gems separate, but that means that you have to reinstall any gems that you might be using in your Rails 2.3 app.  You will probably get plenty of errors when you finally start up your app as a result of the missing gems… I will get into that later.  So here is all you need to do to install the Rails 3 beta 4.

sudo gem install rails --pre

You should be set now with Rails 3.  You can do a version check just to be sure with “rails -v”.

Step 4 – Generate a blank Rails 3 application

NOTE: If you don’t have your app in a source control repository, it may be a good idea to also make a full backup of your app directory before you start changing things.

I found it useful to have a blank Rails 3 app to use as a reference when upgrading my 2.3 app.  You will most likely need to drop some of the files in your 2.3 app or at least copy/paste some of the code.  Create the new app with the rails command in your terminal.

rails new blank_app

Step 5 – Get the rails_upgrade plugin and run it

This is an officially supported plugin from Rails.  Get it here: http://github.com/rails/rails_upgrade Jeremy McAnally has instructions on how to install rails_upgrade as a plugin.  I remember seeing this was opted as a plugin instead of a gem because it was easy to install and delete from your application.

I ran the check rake task first after I installed this plugin.  It gave me quite a large list of things I need to change.  Most of the changes involved the new ActiveRecord Query API, which is pretty awesome btw.  I just ran down the list one by one and changed what it was complaining about.  I re-ran it after every section to make sure the changes were correct.  There are a few other Rake tasks to add the new Gemfile, add the application.rb and fix your routes, but I opted to do that manually in order to learn more about the changes.

I am guessing you may spend alot of time updating queries and scopes, so check out these blogs from some of the Rails gurus for more info on what has changed in Rails 3 and when the old stuff will be deprecated:

http://m.onkey.org/2010/1/22/active-record-query-interface

http://hasmanyquestions.wordpress.com/2010/01/17/let-your-sql-growl-in-rails-3/

Step 6 – Remove your old scripts and add the new rails script

Rails 3 has replaced all the scripts in /app_root/script with a single script called rails.  I deleted all of the files in the script directory and copied the file from my blank application in step 4 and put the file in my script directory.  I think it makes much more sense for rails to have one script file.  This file can start the server, generate scaffolding,  start your console etc..  To see a full list of commands use “rails –help” inside your application somwhere.

Step 7 – Upgrade your environment files (rails_upgrade may take care of this)

The individual environment files in config/environments/ are also a little different in Rails 3.  I am not sure if rails_upgrade takes care of this, but I did not use that rake task, so I just updated the files manually.  You will need to add a block around your configuration in each of these files.  Mine looks something like this for development.rb:

Couponshack::Application.configure do
  config.cache_classes = false
  #.....
end

Also, make sure you update your environment.rb file if rails_upgrade didn’t do it for you.  Refer to the blank application in step 4 for the code… its only a couple of lines now.

Step 8 – Add application.rb (rails_upgrade will do this for you)

If you want to do this manually to see whats new, check out the application.rb file in your blank app.  There are examples in the comments.  For those that don’t know, application.rb is sort of a replacement for environment.rb.  This file is for application specific configuration and it makes sense that they would remove this from environment.rb, since application and environment configuration are really 2 completely separate things.. app configuration is not specific to an environment.

Step 9 – New routes (rails_upgrade will do this for you)

Rails 3 has new syntax and features for the routes file.  Look at config/routes.rb in the blank app you created in step 4.  The comments provide you with an explanation of the new route syntax.  Some of the highlights include shortened syntax for controller/action.  Instead of :controller=>”mycontroller”, :action=>”myaction”, you would write – :to=>”mycontroller#myaction”.   And new syntax for mapping..  instead of “map.connect” or “map.name_of_route”, you just use “match” (and :as=>”name_of_route”).  I would recommend doing this reading all the comments in the new file and doing it manually so you can learn the new syntax, unless of course you have hundreds of routes.

Step 10 – Take care of them Gems (rails_upgrade can do this too)

Along with application specific configuration, Gem config was also removed from environment.rb in favor of the Gemfile which is in the application root directory.  Again, take a look at a blank Rails 3 app to see what this file should look like.  The gemfile makes gem dependency management easy by using the new bundler.  Bundler checks your system for installed gems based on the Gemfile and can install them for you automatically.  If you try to start up your app and you are missing a gem dependency, you will see something like this:

Could not find gem 'mechanize (>= 0, runtime)' in the gems available on this machine.
Try running `bundle install`.

The Gemfile and Bundler are great additions to Rails 3.  For more info check out the bundler home page for more info on what it can do.

I know many of you will run into problems with existing Gems that are not quite Rails3 ready.  Hopefully most of the popular gems and plugins will be finished with the upgrade by the time the official release comes out… probably wishful thinking.  A good resource for troubleshooting problems with Gem and plugin compatibility with Rails 3 is this website:  RailsPlugins.org – Lots of comments have been posted on this site and more than likely someone has already experienced the upgrade problem that you may be having.  It was helpful in troubleshooting a few of the plugin bugs I found.

Step 11 – Beware: “h” helper in views is the default now

This is turned on by default now.  So you don’t have to do crap like this anymore: <%= h @blog.title %>.  The “h” is not necessary, so HTML will be escaped by default.  If you have any content that includes HTML coming from your database, you will need to wrap that with the “raw” method like so: <%= raw(@blog.html_enabled_content) %>

Step 12 – config.ru

Rack-based servers use this file to start the app, so you will probably want to add this if you have not already.  Again, copy the file from the blank app and change the application name.

Step 13 – Start up your app locally and pray

Mine did not work at all.  I use mongrel on my dev box and I had trouble running it manually (mongrel_rails start).  Then I used the rails server command and it seemed to work fine.  You can start it up by using the new rails script file.  All you have to do is type:

rails s

This will start up the server locally and you are ready to take a look at your app.

As far as a I remember, these are the steps necessary for my app to start running on my Mac.  If I left out anything, please comment on this post.  The rest of this post will go into detail on some of the problems I am having and how I solved them, as well as the upgrade path on my production server.  At the time of this writing I am planning on using Nginx in front of Passenger for my production environment.

Problems that I ran into (mainly plugins)

Problem 1 – recaptcha plugin

I was using this in vendor/plugins.  It when I tried to access pages that used this helper, it was complaining about “builder” not being available or something to that effect.  I monkeypatched the code in /plugins/vendor/recaptcha/lib/recaptcha.rb – added the following line to the top:

require 'builder'

recaptcha_tags worked in the views after I added that line.

Problem 2 – will_paginate errors

I was getting this annoying error:

undefined method `paginate' for #<Class:0x16c739c>

activerecord (3.0.0.beta4) lib/active_record/base.rb:1041:in `method_missing'

I included will_paginate in my Gemfile with this line:

gem "will_paginate", :git => "git://github.com/mislav/will_paginate.git", :branch => "rails3"

That did not fix it.  Same error.  I finally realized that I screwed up application.rb when I was upgrading.  I was missing the bundler require call, so the initializers in the railtie were never executed.  Make sure you have this in your application.rb, or your dependencies will probably crap out.

# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)

Problem 3 – equals sign with blocks

When running rails_upgrade, it warned me of several blocks that needed an equals sign. This is new to Rails 3 and it makes sense now that I understand where to use it.  Basically if it spits out a tag or any output to the view, then you should use equals, otherwise if its an .each iterator or something like that that does not produce output don’t use it.  Some of these were false positives like the following:

<% @blogs.each do |blog| %>

Check out Ryan Bates screencast which shows when to use and not use the equals.  I am not sure if this has been fixed in the rails_upgrade plugin, but its something worth noting.  It was spitting out the printed representation of the objects when I used an equals with it.. not pretty ;-)

Problem 4 – my routes.rb is too bloated

rails_upgrade does a very good job of translating your routes.rb into the new syntax for the most part.  This is not a huge deal, but the only part I wanted to change was the way the collection section is written.  My routes.rb after the upgrade was hundreds of lines long.  My legacy code (which needs to be big-time refactored) has lots of collections for my resources.  This was a result of Rails 1 code being merged with Rails 2 code.. I used the collection hash in my resources to add any controller/action that was not RESTful.. and I had lots from my original Rails 1 app.. mostly custom views which could have been consolidated in the show or index action.  But anyways, all these collection members produce something that looks like this:

resources :blogs do
  collection do
    get :action1
    get :action2
    get :action3
    #...... etc.........
  end
end

After experimenting I found out that Rails 3 also accepts this form:

resources :blogs do
  collection do
    get :action1, :action2, :action3 #etc...
  end
end

Just personal preference, but I find this form much easier to manage because of the large number of collection actions that I have.. refactoring them is a whole different story.

Problem 5 – colon in case statement (caused by new Ruby version)

I use the little known, “date_time_text_field_helpers” plugin.  Long name, but came in handy for handling some of my date/time fields.  When I tried to start my app server with Rails 3 it gave me the following error:

/rails_apps/couponshack/vendor/plugins/date_time_text_field_helpers/lib/date_time_text_field_helpers/instance_tag.rb:110: syntax error, unexpected ':', expecting keyword_then or ',' or ';' or '\n' (SyntaxError)
when :hour, :minute, :second : 2

So I looked at instance_tag.rb at line 110 and I see a syntax that I did not recognize in a case statement. I rarely use case statements and I don’t know much about them, but apparently Ruby 1.8x allowed the use of the colon character. This has been deprecated in Ruby 1.9. I changed the colons to “then”. Another problem is the html escaping. I had to update a call to content_tag and pass escape=false. You can check out the changes in my github fork:
http://github.com/johnmcaliley/date_time_text_field_helpers/commit/ba14af26781ac2576f20f0195703fad2c9657082#diff-0

Works just fine now.

more to come…


Tags: , ,

Git Tutorial Part 1: Working with branches and release versions of your Rails app

This week I am working with branches in git for the first time.  Up until now I just used my master branch for everything which works ok since I am the only developer on most of my apps, but now I realize branches will make my life alot easier in more than a few situations.  I am working with my brother on a project where it is necessary for us to use branches, so he started me on the path to git branch knowledge.  Here is my workflow so far.. and most of this relates to working with a Ruby on Rails web app that has stable milestone releases and a master branch for the future version.

I guess lets start with the basics of initializing git and using Github to host your code.  I am assuming you have git installed and you are using Github to host your main repository.. if no, proceed to Google.  Also, I am assuming you already have a project.. Rails or other.

ONE BIG NOTE: REALIZE THAT JUST BECAUSE YOU CREATE A BRANCH OR MAKE A CHANGE AND COMMIT IT ON YOUR LOCAL MACHINE, THAT DOES NOT MEAN THAT IT SENDS IT TO GITHUB OR THE MAIN REPOSITORY!!!  I WAS TOTALLY CONFUSED ABOUT THAT THE FIRST TIME I USED GIT.  LEARN TO PUSH AND PULL!

Go to your project directory, initialize git, add all the files and then make your first local commit with a message

cd /your_app
git init
git add *
git commit -m 'initial commit'

Now you are set up with your local repo. Next you need to login to (or create) your github account. You will create a new repository and name it the same thing as your local app. They will give you instructions similar to what I am telling you here after it is set up. This will add an origin to your local git config and then push all the local files that you just committed to that origin. If you have problems getting this to work, take a look in the github help about security and setting up your ssh keys here

git remote add origin git@github.com:your_username/your_app.git
git push origin master

You will get the following output

Counting objects: 22, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (22/22), 3.73 KiB, done.
Total 22 (delta 3), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
* [new branch] master -> master

What you see here is that your files were transferred to github and a new branch was created called “master”. The new branch was created automatically since there were no branches in this github project. To see what branches you have available on your local machine type:

git branch
* master

Your local machine knows about one branch and it is called master. Lets do some more branching now. My project is already at Version 1.0 but I am just now adding it to Github. Version 1.0 is stable and I may or may not make fixes or small additions to it. I am planning out my development roadmap and some major changes are going to happen in V2. So I don’t want to have to worry about changing and adding a bunch of crap and then realize that I need a minor fix on V1.. that would be a mess because I would have a codebase that was moving toward V2 and I would have to hack and hack in order to deploy that small change back to version 1.. what a mess. Thats where branching comes in. We need a Version 2 branch so we can keep things separate. To create another branch on your local machine do this:

git branch version-1_0-stable

Lets see if it worked..

git branch

OUTPUT:

* master
  version-1_0-stable

Yep.. there is our new branch. But notice that we are still on the master branch. Git puts a asterisks beside the current branch. In order to switch branches on our local machine, we need to use the checkout command.

git checkout version-1_0-stable

OUTPUT:

Switched to branch 'version-1_0-stable'

If you run “git branch” again, you will see a star beside version-1_0-stable. Wouldn’t it be easier if you could branch and switch all in one command? Oh yeah.. you can. Why didn’t I just say that in the first place?? Who knows, why I do alot of stuff… Here is how you delete your branch and then create it with the one-liner. Note that you can’t delete a branch you are currently on, so you must checkout back to master first:

git checkout master
git branch -D version-1_0-stable
git checkout -b version-1_0-stable

By throwing in that -b flag, you are telling git to create the branch and then switch to it all in one move.

Ok, cool, so I got another local branch. Why don’t I see the new branch in Github?? Because I have been working with git branches locally. Github is not aware of these changes because I have not pushed anything to it. You can see what github has by logging in and clicking on the “Switch Branches” dropdown in your project or by using the -r flag on your local box -> “git branch -r”. So now we need to tell Github about this branch.

git push origin version-1_0-stable

OUTPUT:

Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
 * [new branch]      version-1_0-stable -> version-1_0-stable

Frickin awesome dude… I gots a new branch and github has it too. So I have 2 branches that I can work in separately. Most of my work will be in the master branch which is on the path to version 2, but if something craps out in V1 and I have to put in a bug fix, I just “git checkout version-1_0-stable”, make my changes, commit them, and push them to the the V1 branch. Then I could easily deploy the new code without worry about V2 code getting in the way. Lets try that out:

You are already using version-1_0-stable. So go to your editor of choice and change a file. You must commit the changes locally first. This will commit to only the current branch, so master will not get these changes since you are on version-1_0-stable. You can commit your files several ways. Here are the 2 ways I do it.
1. Commit all local changes at the same time and use -v to view the changes

git commit -a -v

2. Commit a single file if you have other outstanding changes that you don’t want committed yet. Just use the relative path to a single file (or * wildcard it for a whole dir).

git commit -v path/to/your/file

Now your local machine knows about the committed files, but not Github. You have to push.

git push

OUTPUT:

warning: You did not specify any refspecs to push, and the current remote
warning: has not configured any push refspecs. The default action in this
warning: case is to push all matching refspecs, that is, all branches
warning: that exist both locally and remotely will be updated.  This may
warning: not necessarily be what you want to happen.
warning:
warning: You can specify what action you want to take in this case, and
warning: avoid seeing this message again, by configuring 'push.default' to:
warning:   'nothing'  : Do not push anything
warning:   'matching' : Push all matching branches (default)
warning:   'tracking' : Push the current branch to whatever it is tracking
warning:   'current'  : Push the current branch
Counting objects: 14, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 815 bytes, done.
Total 9 (delta 5), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
   d8b9507..862d326  version-1_0-stable -> version-1_0-stable

Look at all those warnings. WTF is git talking about?? They want us to type more options in?? Well.. I actually think this is crap, but there is probably a good reason behind the default behavior. By default it is going to push all branches that it finds on your local box over to the origin server. So for instance, if you change and commit something on master, switch branches to V1 and change and commit something on that branch too, then git will push both of them. That could potentially be dangerous if you were not ready to push master or V1. So in order to fix that you need to pass your git config some options to make it only push the current branch you are working in.

git config push.default current

If you look at /your_app/.git/config, then you will see it added some options to your config.

[push]
        default = current

Next time you issue the git push command the warning goes away and it only pushes the current branch. Thanks for not being an asshole this time git… I really appreciate it.

So we are looking pretty good, but there is one more thing that is bugging me. What if I make a change to V1 and I want the changes to show up in V2 also. The bug is most likely in V2, unless I removed that feature or completely re-coded it. So how do I get them darn changes over to me other branch?? Move back to master (V2), maybe check the differences so you don’t totally mess it up, and then issue the merge command:

git checkout master
git diff master version-1_0-stable
git merge version-1_0-stable

If you have any conflicts, the merge might not work and it will tell you to fix them. Here is the message “Automatic merge failed; fix conflicts and then commit the result.” So how do I fix the conflicts? Go to the files that it marked as conflicting. In my case, my LICENSE file was conflicted so it said:

Auto-merging LICENSE
CONFLICT (content): Merge conflict in LICENSE

So I look at the file and git has put some junk in there that shows where the conflicts are and which branch they are coming from. Just edit the file to make it work and get rid of the junk (<<<<<<<<<<).

<<<<<<< HEAD:LICENSE Copyright (c) 2010 John McAliley  ======= Copyright (c) 2009 John McAliley >>>>>>> version-1_0-stable:LICENSE

Hmm.. its 2010, the file should say that, so I edit and make it look like this:

Copyright (c) 2010 John McAliley

Then I do a “git commit -a” and the conflict has been resolved. If you issue a “git merge version-1_0-stable” then it will tell you its up to date. Cool, I guess we are done. Not so fast chief!!! Don’t forget to push it to Github

git push

Now we done! The change in V1 is also in V2. Go drink a beer. You deserve it.

I think that about covers my workflow as it stands now aside from pulling using the rebase command. Yehuda Katz has an excellent tutorial on his Git workflow regarding pulling, pushing and resolving conflicts (although he does not go into branching). I have only been doing branching and versioning in git for a week, so I am sure some of you git masters might have a better workflow. If so, don’t be stingy.. leave a comment and show people a better way. Next tutorial will cover tagging as related to minor version changes in the master before it is actually considered stable. Check out the ruby on rails source in github if you can’t wait. They use branches and tagging for major and minor releases (including betas in the master branch)

Tags: , , , , ,

Custom Rails config file containing application constants

NOTE: I am not using this method now.  Take a look at settingslogichttp://github.com/binarylogic/settingslogic

I have used several methods to store app config such as putting them in environment.rb or including a module in the lib directory, but I stumbled across a method today on Stack Overflow that I really like. There are three easy steps involved in setting up a config file in Ruby on Rails for your application constants using this method.

1. Create a new yaml called app_constants.yml in /rails_app/config/ like so

defaults: &defaults
  my_const1: some value
  my_const2: some other value

development:
  <<: *defaults

test:
  <<: *defaults

production:
  <<: *defaults

2. Create a new file under /rails_app/config/initializers called app_constants.rb like so:

APP_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/app_constants.yml")[RAILS_ENV]

3. Access your config variables using the APP_CONSTANT variable like so:

my_const1 = APP_CONFIG['my_const1']

The reason this works is that rails runs the code in the initializer you just created when you start up your app server. It sets the APP_CONFIG constant to the YAML content using the load_file method. This will return you a nice hash that you can access your application constants with. Simple and powerful at the same time. If anyone has a better suggestion to do this (not including the database), or sees any issues with this, please leave a comment. Thanks!

Tags: , ,

Capistrano PATH error – command not found: mongrel_rails

I was setting up one of my servers so I could deploy with Capistrano.  I finally spent some time to read the documentation and learn how to use it.  Before I was just logging in and running a shell script that I had written that pulls code from github, rakes the db and restarts mongrel.  While this worked ok, I figured it would be easier to use cap.  I followed the documentation and read a tutorial or two and everything worked fine until cap tried to restart my mongrel cluster.  Then I get a dreaded error like this:

*** [err :: 123.456.789.123] /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31: command not found: mongrel_rails start -d -e production -a 127.0.0.1 -c /rails_apps/myheatmap/current -p 8200 -P /rails_apps/myheatmap/shared/pids/mongrel.8200.pid -l /rails_apps/myheatmap/shared/log/mongrel.8200.log

I spent at least 2 hours trying to figure a way to make this work, without doing an ugly hack.  When you get this error it means that mongrel_rails is not in the environment PATH that Capistrano is using.  It is using just a basic path that does not include /usr/local/bin.. which is where most mongrel_rails executables are available.  There are several suggestions out there that I could not get working, but they may work for you.  So here is a list:

1. Update your mongrel recipe in your gem directory <gems>/<mongrel_cluster_dir>/mongrel_cluster/recipes_1.rb

I tried this first, even though its a total hack.  I just hardcoded the path to /usr/local/bin/mongrel_rails where the mongrel_rails variable is set.  It calls that file correctly, but that was not my problem.  When I looked at cap logs it actually found mongrel_rails cluster::restart without the path.. so that is pointless.

2.  update your sshd_config and add ~/.ssh/environment

Check out this thread – http://rubyforge.org/pipermail/mongrel-users/2007-July/003757.html

It worked for the OP, but not for me.  I updated /etc/sshd_config and uncomment this “#PermitUserEnvironment yes” (you have to change it to yes..default is no).  Then I added ~/.ssh/environment for my deploy user and restarted sshd “>/etc/init.d/sshd restart”.  Tried to deploy again and still get the same error.

3.  add default_environment["PATH"]=”/usr/local/bin:yadayadayada”

Suggested here (same thread as #2)

This seemed to have no effect also.  Actually on all of these I tested the path with run “env” in my cap recipe and I would get the correct path, but I guess the path is being reset along the way somewhere.  no clue…

4. SOLUTION THAT WORKED FOR ME

I finally broke down and did what I really did not want to do in the first place.  I really fix it to use the correct path, I just added a link to mongrel_rails in /usr/bin.  Cap was able to find it in there and the script worked.  Not ideal, but I really can’t invest any more time into figuring this out.  Time investment vs ROI is already looking pretty bad for me on this one.  Now for every server that needs to use cap and mongrel, I will need to create the link

cd /usr/bin
ln /usr/local/bin/mongrel_rails

Other resources on this problem:
http://groups.google.com/group/capistrano/browse_thread/thread/1fc5b396b577c34c

http://stackoverflow.com/questions/2555720/sudo-issue-with-capistrano-could-not-find-rubygem-mongrel

http://osdir.com/ml/lang.ruby.capistrano.general/2006-12/msg00077.html

If anyone has a better way to do this or has detailed instructions on getting #1 – #3 working, please leave me a comment.

Tags: , ,

Change a column data type with Rails migration

I always forget the syntax for this and I have to look it up, so I am going to post it here.  Maybe this will help some others out.  Basically, what I need to do is change the data type of a column in my database table.  The table “widgets” contains a column/field named “count”.  I want to change count from an integer to a float.  So first I create a rails migration with the following command.

>script/generate migration change_data_type_for_widget_count

Then I edit the migration file:   app_root/db/migrate/20091007151516_change_data_type_for_widget_count.rb.  Here is the migration syntax to change the data type of a column:

Then you just run the rake task “db:migrate” and it will change the data type in the database table.

>rake db:migrate

Tags: ,

Time Zone troubles

Well… I have been ignoring this for a few weeks, because it is not a huge deal, but I thought I might as well fix this.

Problem: The created_at and updated_at dates do not match my system time.  Every time I save a record, ActiveRecord records the date 5 hours ahead of my local time.  This was messing up some of my cronjobs that run 5 minutes before midnight.  Some of the records were not being found, because the date was recorded five hours in the future.

Solution:  After some digging on Google, I figured out that this is set in config/environment.rb.

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run “rake -D time” for a list of tasks for finding time zone names.
config.time_zone = ‘UTC’

I did some more digging and found that UTC is 5 hours ahead of ET, so ActiveRecord was using this timezone instead of my local timezone.  The first fix I tried did not work, which was to chagne config.time_zone =”Eastern Time (US & Canada)’  I restarted my app server and still saw created_at was five hours ahead of my server’s system time.  So then I just commented it out and restarted the app again.  Walah.. it works now.  So the correct config to use system timezone is as follows:

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run “rake -D time” for a list of tasks for finding time zone names.
#config.time_zone = ‘Eastern Time (US & Canada)’

No more worries

Tags: ,