Posts Tagged rails3

Add existing records to the versions table in paper_trail

I was working with paper_trail today. It is an excellent gem that saves a history of your creates, updates, and destroys for Rails ActiveRecord models. I am adding this gem to an existing project that already has records in the database. I need to put these records in the paper_trail’s versions table, so I can use a query that checks to see how many versions exist for a particular model instance. Just to make it clean, so I don’t have to query both the model and the versions table, I want to add that initial “create” version for all of my existing records. After reading through the source, I could not figure out an easy way to do it with the existing codebase, so I decided to create a simple monkey patch and rake task that will version all of my existing records.

This rake task adds a new instance method, so the model can have access to the private method “record_create” in paper_trail. Then it takes all model classes, iterates through them and detects the classes that have paper_trail enabled. If the record has no versions, then it creates the initial version and prints out the result. Now I have a fresh set of data that makes it look like paper_trail was installed to begin with (minus any history I’ve already lost).

Tags: ,

Why you can’t reopen Rails 3 Engine classes from the parent app

My main frustration with Rails 3 engines is not being able to reopen a class from the parent application.  The first time I used Engines, I tried this:

#authentication stuff in my engine
/my_engine/app/controllers/users_controller.rb

#reopen the users_controller.rb in the parent app and override a method or add a new method
/my_app/app/controllers/users_controller.rb

This can be useful for several reasons:
1. Adding methods to your engine’s models, controllers etc from the parent app.
2. Overriding engine methods from the parent app.  In the example above, I wanted to add some behavior to the create method.

Although not everyone will care about the internals of a widely used open source engine, many developers will find this useful when they write their own engines. You don’t want to have to copy the source from an engine controller or model over to your parent app, just to add or overwrite a method.

If you try to reopen the class using the method at the beginning of this post, then your engine UsersController class is never loaded. The app will instead load the users_controller.rb within its own “/app/controllers” directory. I figured their must be a sane reason for this… and I wanted to get to the root of the problem, so I started digging in the Rails source. From first glance, it looks like this restriction was possibly unintentional (although I don’t know enough about the internals to say for sure). Let’s see what is happening inside Rails:

The engine’s app directory will be in the eager_load_paths and it will hit the “require_dependency” line. “require_dependency” is part of ActiveSupport and it will search for the filename by iterating through the autoload paths and finding the first match in that autoload paths list. Here is the code in ActiveSupport (look at “search_for_file”):

debugging the autoload_paths produced the following result (with a few things removed for readability):

As you can see from the array above, the app paths show up before the engine paths.  As a result, Rails will always search for the file in the parent app’s paths before it searches in the engine’s paths, so *OUCH* it is not possible to re-open the class.  There are probably ways this can be done.. like maybe requiring the files explicitly in an initializer in the engine, but I would rather not resort to such hacks.  My temporary fix is to use class_eval in the parent app, but this is still really ugly.  Anyways.. its getting late and I am done for tonight. I guess I will keep poking around to see if there is a suitable workaround.

UPDATE

After looking around SO, I found a patch for ActiveSupport::Dependencies

With this patch, the require_or_load method is aliased and the engine classes are loaded before the original method is called. Gave it a quick test and so far so good.

Tags: ,

Working with ApplicationController in a Rails 3 Engine

UPDATE 02-13-2011: It appears that approach #2 does have a downside after all. Given my current implementation, dev will not reload the class b/c config.cache_classes is set to false. I might still try this approach using config.to_prepare.. I will update the post when I have a chance to experiment more.

Rails 3 Engines use a standard Rails app directory setup by default, so logically, you might think that you can just drop in /app/controllers/application_controller.rb, and reopen the class. This is not the case. Rails will ignore any files that have the same name as a file in the parent application. Seeing as how every Rails app has “/app/controllers/application_controller.rb”, the engine has no chance of loading this file. Poor little engine. But if your engine has a strong will… I think I can, I think I can.. then there are other ways to get your methods inside the parent app’s ApplicationController. Let’s take a look at some of the choices you have.

Option 1 – ActiveSupport.on_load

Lets start by looking at one of the most popular Rails plugins -> Devise. At the time of this writing, Devise has a helpers module that defines a class method called “define_helpers”. A mapping (user or admin) is passed to the method and several instance methods are defined using class_eval. Then these instance methods are included in the ApplicationController using the “helper_method” function. Check out how this is acheived:

ActiveSupport.on_load(:action_controller) do
  helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
end

on_load.. reminds me of something you would see in a javascript library. So when ActiveSupport loads the ActionController, the helper methods are inserted. You could also use a different approach within this block. Let’s say you have defined a module with submodules for ClassMethods and InstanceMethods. You would just extend/include them in the block. The most common approach for Rails 3 engines is using an initializer in the Engine class.

# You can put this in /myengine/app/models/ and it will load it automatically (or put it in your engine lib directory and require it)
module MyModule
  module ClassMethods
    def some_awesome_class_method
    end
  end
  module InstanceMethods
    def some_awesome_instance_method
    end
  end
end
#/myengine/lib/myengine/engine.rb
require "myengine"
require "rails"

module MyEngine
  class Engine < Rails::Engine
    initializer 'myengine.app_controller' do |app|
      ActiveSupport.on_load(:action_controller) do
        extend MyModule::ClassMethods
        include MyModule::InstanceMethods
      end
    end
  end
end

There are plenty of variations of the extend/include stuff that use ActiveSupport::Concern, “self.included”, “self.extended”, but they all achieve basically the same thing… adding instance and class methods.

Option 2 – Reopen the class

In the intro of this blog post, I said that Rails will not allow you to load classes from an engine with the same name as the parent app. When it comes to the ApplicationController, I am not sure if it was the intent of the Rails Core to deter you using the same name/format as the application_controller in a regular rails app… there may be use cases that I am not aware of where this is harmful or lacks functionality. It makes sense that other Engine classes with the same name/path should not be loaded since you would want the parent app to override them if it has the same controllers. This could result in clashes and unexpected consequences. But, I see the ApplicationController as a bit of a anomaly in this situation. Unless your parent app has tons of methods in the ApplicationController and the engine ApplicationController is using common method names, I don’t see this as a problem. Use fairly unique method names or prefix the method name with your plugin name if you are worried about it. There is a simple workaround to allow you to use the same format for ApplicationController that you are accustomed to seeing a standard Rails app… just prefix the file name with the plugin name for the ApplicationController. For example: /app/controllers/myplugin_application_controller.rb. Then you can add code to this file, just as I would with a Rails app ApplicationController. Check it out:

#myplugin_application_controller.rb
class ApplicationController < ActionController::Base
  before_filter :this_filter_is_the_best_thing_since_sliced_bread

  def this_filter_is_the_best_thing_since_sliced_bread
  end

  def really_cool_instance_method
  end

  def self.really_cool_class_method
  end
end

I think technically you are not reopening this class, since it gets loaded first.. so lets say we are defining the ApplicationController and the parent application is reopening the class. I did some quick tests and the Engine ApplicationController loads before the parent ApplicationController. I also wrote a spec to make sure all filters were intact using this method:

# get a list of the filter names from ApplicationController
filters = ApplicationController._process_action_callbacks.select { |c| c.kind == :before }
filter_names = filters.collect{|filter|filter.filter}
filter_names.include?(:this_filter_is_the_best_thing_since_sliced_bread).should be true #yup
filter_names.include?(:a_filter_defined_in_the_parent_app_controller).should be true #yup

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 Rails 3 on Mac OSX Snow Leopard 10.6.4

I finally made the complete switch to Mac this weekend.  I have been using my iMac at work for about a year and I love it, but I have been holding on to my Dell laptop and more and more every day I feel like throwing it through a window, so I decided to bite the bullet and buy a Macbook Pro so I could be more productive at home.  Here is a step by step guide of how I set it up to work with Rails 3.

1. Install git and RVM

RVM is a great way to manage multiple ruby versions on your Mac, and it also makes installing any version dead simple.  You will need to install “git” before you can install RVM.  I downloaded the git DMG from here:

http://git-osx-installer.googlecode.com/files/git-1.7.3.2-intel-leopard.dmg

Install git from the DMG and then you can install RVM afterwards.  Note that you will have to restart your terminal session in order to use the git command.

Install RVM by issuing the following commands in your shell:

Now you should be good to go with RVM. Here is the install guide if you run into any problems: http://rvm.beginrescueend.com/rvm/install/

Install a C Compiler (by installing XCode)

You will need a c compiler to install ruby using RVM.  You can download XCode, which has a c compiler, from the Apple website.  I downloaded the iOs/XCode package since i will probably be developing iPhone apps as well.  This is a mamoth download (3+ GB), so be prepared to wait a while.

If you dont feel like waiting that long for the download, pop in the OSX DVD that came with your Mac and when the startup screen appears, choose “Optional Installs”, and you can install XCode from there (without iOS stuff).

Install Ruby 1.9.2

cowboycoded$  rvm install 1.9.2

Simple as that!  This may take a few minutes to compile and install.  RVM will also install RubyGems, so its not necessary to do that manually.  Snow Leopard ships with Ruby 1.8.7, so you will need to switch to version 1.9.2 after you install it with RVM.  Use RVM to switch versions.

cowboycoded$  rvm 1.9.2

Verify that you are using 1.9.2

cowboycoded$  ruby -v

Set 1.9.2 as the default Ruby in RVM, so you don’t have to switch every time you open a new shell

cowboycoded$  rvm --default use 1.9.2

Install Ruby on Rails 3

cowboycoded$  gem install rails

This will install Rails 3 and all of its dependencies.  At the time of this writing the current version was 3.0.3.  I use MySQL for all of my apps, so I need to install that also.

Download and install MySQL server

http://dev.mysql.com/downloads/mysql/

Its probably easiest to download the DMG and install it that way.  Get the 64 bit DMG if you are using Snow Leopard.  Probably a good idea to install the MySQL Startup Item that is in the DMG as well.  This will start MySQL when your Mac boots.

After you install you can use the Startup Item to start MySQL manually:

cowboycoded$  sudo /Library/StartupItems/MySQLCOM/MySQLCOM start

If you are using the mysql gem (v2.8.1), then you will likely run into a problem when you bundle install. Install the gem manually using this command instead:

cowboycoded$  sudo env ARCHFLAGS="-arch x86_64" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

Test it out

You should have everything you need at this point.  Fire up a new rails app and make sure everything worked:

cowboycoded$  rails new test_app
cowboycoded$  cd test_app
cowboycoded$  bundle install
cowboycoded$  rails s

Tags: , , , , ,

Rails 3 – rails.js – document.on is not a function

I was busy converting an old app to Rails 3 today and I ran into some problems with Prototype and the new UJS stuff in Rails 3.  I set up a link_to with method delete like so:

= link_to "Delete", widget, :confirm=>"Are you sure you want to delete?", :method=>"delete"

With the new UJS setup in Rails 3, the link_to helper will create a tag like this when you use confirm and method delete:

<a rel="nofollow" data-method="delete" data-confirm="Are you sure you want to delete?" href="/widgets/123">Delete</a>

The new rails javascript file takes these parameters and uses Prototype to show you the confirm alert box and also makes it RESTful by using the DELETE method.  I was a bit perplexed when I did not see the alert box and this link used the GET method instead of DELETE.  I took a look at my error console in Firefox and got this error:

document.on is not a function

What the crap??  So I proceeded over to /public/javascripts/rails.js to see what was going on.  They bind the click event for the <a> using the following code:

  document.on("click", "a[data-method]", function(event, element) {
    if (event.stopped) return;
    handleMethod(element);
    event.stop();
  });

Apparently this does not work with the stable version of Prototype. It does work with Prototype 1.7, but I like the google hosted javascript files, and they use the stable version of 1.6. So I began questioning why one of my other apps was working just fine. Turns out that rails.js was changed with Rails 3 official release and I was using the betas for my other app. So I just took the old rails.js file from my other app and put it in this app. Here is an old version of rails.js on github

You could also upgrade to the 1.7 release candidate and use the newer rails.js file that ships with Rails 3 official, but I will stick with the Google hosted prototype. Hopefully this does not break anything else in my app ;-)

Tags: , , ,

Autoload lib in Rails 3

In contrast to Rails 2, Rails 3 does not automatically load your /rails_root/lib.  According to Jose Valim this was intentional.. read more here -> https://rails.lighthouseapp.com/projects/8994/tickets/5218-rails-3-rc-does-not-autoload-from-lib

For those of you who still want this functionality in your Rails apps, you just need to add 2 lines to config/application.rb.  Here ya go:

config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]

Tags: ,

Careful with @results.delete method in Rails 3. You may not be working with an Array

I was working in a method that needed to remove an element from the resultset that was returned when I called this:

@widgets = Widget.active  #active is just a scope I created in the Widget model
@widgets.delete(some_widget)

I would expect this to delete the object from the Array and my view would not render that particular element in the collection. But I found this element being deleted from the database. How is that possible??? It turns out that this collection was still an ActiveRecord::Relation object and not an Array. Since the new query API lazy loads the queries, they are not executed until they get to the view… and not turned into an Array object until they get to the view. I was trying to remove an element from the Array in my controller. To figure this out, I just put a debug statement to return the class of @widgets.

p @widgets.class
#=>ActiveRecord::Relation

In order to force the Relation into an Array, you can call the “all” function which will eager load it in the controller.

@widgets = Widget.active.all  #call all to eager load
@widgets.delete(some_widget)

This time, “some_widget” was only removed from the @widgets Array and not deleted in my database.

Tags: , , ,

Using 2 sources for a gem in different environments with Bundler

Bundler makes life much easier for us Rails devs, but there is currently one thing on the wishlist that I would like to see implemented.  Bundler already lets you specify a local path for a gem and also lets you choose github as the source of the gem.  My problem is that Bundler does not allow you to add the same gem in development and production with different sources.  Why do I need multiple sources?  I work with quite a few custom gem engines in my rails apps and sometimes I run into issues that I need to debug or I find new features that I want to add to one of my gems.  It would be a real pain to git commit, push, bundle update and then restart my server every time I need to change something in one of my custom engines and view it in my rails app.

Here is what I was trying to do initially before I understood how Bundler worked with environment groups:

group :production do
  gem 'mygem', :git => 'git@github.com:me/mygem.git'
end

group :development do
  gem "mygem", :require => "mygem", :path => "/gem_dev/mygem"
end

Bundler no likey: You cannot specify the same gem twice coming from different sources.

I suspected I was not the only one that wanted something like this, so after looking at the github issue list I found a simple solution in the comments for this wishlist issue .  Use an environment variable and put a condition in your Gemfile like so:

if ENV['MY_BUNDLE_ENV'] == "dev"
  gem "mygem", :require => "mygem", :path => "/gem_dev/mygem"
else
  gem 'mygem', :git => 'git@github.com:me/mygem.git'
end

Don’t forget to set your environment variable in you command shell:

export MY_BUNDLE_ENV='dev'
bundle install

Now I can work with the gem from my local path. But, lets say I want to deploy to production. There are 2 ways you can approach the production deployment:
Option 1: For those that live on the edge, you will keep your Gemfile pointing to your master github repo
Option 2: For those who are scared to live on the edge, you should probably tag your code in github for your latest stable version and add your tag , :tag=>'v1.0.0' to the end of the production gem line.

Either way, you will need to install your bundle locally and then commit your Gemfile.lock (and Gemfile if changed) to github.  I tried a rake task to do this at first, but since rake loads your app, it will crap out and ask you to bundle install before the task can actually change the ENV var (I think that was the problem??).  I wasted quite a bit of time trying to figure out a workaround using rake with no luck, so against my will I created a simple shell script to speed up the process.

APP_PATH="/path/to/your/rails_app"
cd $APP_PATH
export MY_BUNDLE_ENV='prod'
bundle install
git commit -m 'new Gemfile' $APP_PATH/Gemfile
git commit -m 'new Gemfile.lock' $APP_PATH/Gemfile.lock
git push
export MY_BUNDLE_ENV='dev';
bundle update

This script sets your environment variable to “prod”, runs a bundle install and then commits and pushes your Gemfiles to github. Then it sets the environment var back to dev and runs a bundle update (don’t need install since you are using local paths) so you can keep working locally after the push.  Now your app has the proper gems in Gemfile.lock in your github repo and the last step is to deploy it to prod… I use Capistrano and my app server is Passenger.  Here is my recipe for the bundler step (ripped off from somewhere else of course ;-) )

namespace :bundler do
  task :create_symlink, :roles => :app do
    shared_dir = File.join(shared_path, 'bundled_gems')
    release_dir = File.join(current_release, 'vendor', 'bundle')
    run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
  end

  task :bundle_new_release, :roles => :app do
    bundler.create_symlink
    run "cd #{release_path} && bundle install --deployment"
  end
end

after "deploy:update_code" do
  bundler.bundle_new_release
end

UPDATE: Looks like a deployment cap recipe is included in Bundler now, so you should probably use that instead. MAN, THESE GUYS MOVE FAST!!!
This recipe will run Bundler deployment after the code has been updated (so it can see the new Gemfile). Works well for me so far. It would be easier if Bundler supported the first syntax I tried, but this ain’t to difficult to add.

As always, comments and corrections are welcome.

UPDATE 2: This issue will be fixed in version 1.1 according to @wycats

mrreynolds: @wycats Any chance Bundler could support multiple sources for one gem per env? Otherwise we need such hacks:http://bit.ly/bjdk0C

wycats: @mrreynolds the :git/:path issue is scheduled for 1.1

Tags: ,