developer, designer, consultant

rails

Rails — A faster way for next and previous links on a post, article, or any model

Stamped: 12 Jan 2012 | Show comments

This is a simple, but common question I come across when dealing with people learning rails, as they inevitably do a bloggish type app first.

I assume you have a model like the one below

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

Adding the next/prev links

Personally, I prefer to add instance methods as well as a scope or a class method. Check it out below:

class Article < ActiveRecord::Base
    scope :next, lambda {|id| where("id > ?",id).order("id ASC") } # this is the default ordering for AR
    scope :previous, lambda {|id| where("id < ?",id).order("id DESC") }

    def next
      Article.next(self.id).first
    end

    def previous
      Article.previous(self.id).first
    end

    #### used below to benchmark
    # scope :next_by_date, lambda {|created_at| where("created_at > ?",created_at).order("created_at ASC") } # this is the default ordering for AR
    # scope :previous_by_date, lambda {|created_at| where("created_at < ?",created_at).order("created_at DESC") }

    # def next_by_date
    #  Article.next_by_date(self.created_at).first
    # end

    # def previous_by_date
    #  Article.previous_by_date(self.created_at).first
    # end

end

How it works

If you're confused, don't be! Let's assume we have an array of article ids: [1,2,4,6]. If we're looking for the next article for 2, it would obviously be 4. Programmatically, you can't just say Article.find(id+1), because it won't exist in some situations where articles are deleted.

We have to find the next id above 2, hence where("id > ?", id). However, if you order the ids descending you'll get 6, because 6 is the highest in the list while still being larger than 2. Knowing that, we then sort the list ascending, because 4 will be first on the list when ordering ascending.

This, of course, assumes you're using the instance methods as I did, if you used .last instead, the ordering for each would be reversed.

It's slightly faster than sorting by a date, or any other field not indexed

Since you're sorting by the primary key, it should be much faster than sorting by any other field, as the primary key is usually indexed in most databases. Plus, it's computationally easier to sort an integer, however slightly.

For example:

sql = "INSERT INTO articles (title, body, user_id, created_at) VALUES "
array = []
n = 100000
n.times do |i| 
  array << "('#{i}','#{i}', 1, '#{eval("#{i}.days.ago")}')"
end
sql += array.join(",")
ActiveRecord::Base.connection.execute(sql)
Benchmark.bm do |x|
  x.report { Article.first.next_by_date }
  x.report("by date: ") { Article.first.next_by_date }
  x.report { Article.first.next }
  x.report("by id: ") { Article.first.next }
end

I ran each twice just incase some caching occurred, and the results were:

      user     system      total        real
by date:   0.000000   0.000000   0.000000 (  0.017141)
by id:     0.000000   0.000000   0.000000 (  0.000682)

Changing the repetition will demonstrate a linear pattern, say O(n), for finding the next by, whereas by date will demonstrate a more exponential pattern, perhaps nearing O(n2).

tags: rails

Migrating from Rails 3.1 RC4 to RC5 using Heroku's Cedar Stack (also compass, unicorn, and sendgrid)

Stamped: 08 Aug 2011 | Show comments

A mouthful, indeed

I'm currently developing a super duper secret project which uses Rails 3.1 and is hosted on Heroku. With the release of Rails 3.1 RC5, I've decided to migrate to Heroku's Cedar stack after reading Michael's article about how easily unicorn can be implemented on the new stack.

I've already migrated one app, Wasted on Steam, and the performance enhancements are amazing, as you might expect. Using siege, it was able deal with 100 concurrent requests repeated 10 times in 2.5 seconds per request. Previously, it had taken 10 seconds per request. This is amazing, especially considering Wasted on Steam uses a network blocking request to update data.

For this other app, this super duper secret one, I thought I would detail the process.

The initial setup

Currently, there's no way to migrate the stack automatically, so we have to clone and proceed:

git clone git@heroku.com:<your_app_name>.git cedar_testing

Next, create the app on the new stack:

heroku create --stack cedar
git config heroku.remote heroku

The code changes

Gemfile fixes

Rails 3.1.rc5 separated the asset-building gems into their own group in the Gemfile, called :assets, so we need to make some changes and clean it up so it looks like this:

source 'http://rubygems.org'

gem 'rails', '3.1.0.rc5'

group :assets do
    gem 'sass-rails', "~> 3.1.0.rc"
    gem 'coffee-rails', "~> 3.1.0.rc"
    gem 'uglifier'
    gem 'compass', '0.12.0.alpha.0.91a748a', :git => 'git://github.com/chriseppstein/compass.git', :branch => 'rails31'
end

gem 'pg'
gem "kaminari"
gem "redcarpet"
gem "devise"
gem 'sprockets'
gem "cancan"
gem "mime-types"
gem "treetop"
gem "ancestry", '>= 1.2.2'
gem "nokogiri", '1.4.4'
gem "oa-oauth", :require => "omniauth/oauth"
gem 'oa-openid', :require => 'omniauth/openid'
gem 'omniauth', '>= 0.2.6'
gem 'dalli'

group :production do
    gem 'unicorn'
end


group :development, :test do
    ...
end

Miscellaneous fixes

Please note, we no longer need to include the old version of rake, nor the therubyracer (the Cedar stack now includes node.js). It's very important that you update the path to use the native node.js by typing:

heroku config:add PATH=vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:/bin:bin

Supposedly, this is supposed to happen on new apps by default, but I had issues. Now, make sure you run bundle update now to update the Gemfile!

Next, we need to update our application.rb; it's changed in RC5. Around line, replace with the following:

Bundler.require *Rails.groups(:assets) if defined?(Bundler)

Now, if you're using Compass like I am, you'll need to create an initializer in config/initializers/sass.rb:

Rails.configuration.sass.tap do |config|
  config.load_paths << "#{Gem.loaded_specs['compass'].full_gem_path}/frameworks/compass/stylesheets"
end

Thanks to Ken Collins for that bit! I imagine, as some point when Rails 3.1 is released this will be done automatically.

Next, in your production.rb, change the config.action_dispatch.x_sendfile_header to:

config.action_dispatch.x_sendfile_header = nil

If you use sendgrid

The Cedar stack is supposed to be less magical and black-boxy then the others, so if you use sendgrid, we have to accommodate for it in our production.rb.

config.action_mailer.raise_delivery_errors = true
config.action_mailer.smtp_settings = {
  :address        => "smtp.sendgrid.net",
  :port           => "25",
  :authentication => :plain,
  :user_name      => ENV['SENDGRID_USERNAME'],
  :password       => ENV['SENDGRID_PASSWORD'],
  :domain         => ENV['SENDGRID_DOMAIN']
}

Using unicorn instead of thin

Our Procfile

This Procfile tells Heroku exactly which process groups need to exist for various reasons. Like a web group to run our unicorn. Our Procfile is quite simple as we only need the main web process, Unicorn.

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb

The unicorn config

Place this in your config directory and call it unicorn.rb

worker_processes 4 # amount of unicorn workers to spin up
timeout 30         # restarts workers that hang for 30 seconds

You'll have to play around with the configuration depending on how memory-heavy your app is. Wasted on Steam uses a ton of gems and it can easily run 4 worker processes.

Push the changes to heroku

git add Procfile config/unicorn.rb
git commit -a -m "Updated for Heroku and Unicorn"
git push heroku master

The data

If you have any configuration variables, you'll want to go to the old app and do heroku config -s and migrate them over using heroku config:add <VARIABLE>=<DATA> <HELLO>=<MOREDATA>.

Finally, we have to pull the database from our old app. This will only work if you have the gem taps installed, so gem install taps. Go to your old app folder and type the following:

heroku db:pull sqlite://backup.sql
mv backup.sql ../cedar_testing
cd ../cedar_testing
heroku db:push sqlite://backup.sql

If you have any issues, simply type git remote rm heroku; git remote add heroku git@heroku.com:<new_app_name>.git then proceed with the steps above. This usually fixes everything.

We're done!

Hopefully, everything should work now as planned. Make sure you go to http://<yourapp>.herokuapp.com and not http://<yourapp>.heroku.com, and you should see the site!

Comparing performance

This app is a simple app, but to give you an idea of how it performs, again with 100 concurrent requests repeating 10 times:

Old & Busted

Transactions:                 995 hits
Availability:              99.50 %
Elapsed time:              62.90 secs
Data transferred:           1.35 MB
Response time:              5.26 secs
Transaction rate:          15.82 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               83.19
Successful transactions:         995
Failed transactions:               5
Longest transaction:           15.67
Shortest transaction:           0.12

New hotness

Transactions:                1000 hits
Availability:             100.00 %
Elapsed time:              19.99 secs
Data transferred:           1.36 MB
Response time:              0.61 secs
Transaction rate:          50.03 trans/sec
Throughput:             0.07 MB/sec
Concurrency:               30.33
Successful transactions:        1000
Failed transactions:               0
Longest transaction:           14.04
Shortest transaction:           0.11

Many articles helped get me here

Heroku's articles on migrating helped a lot. Michael's article on using unicorn on Cedar was the inspiration, and finally, Ken's article helped me get Compass integrated. Thanks!

tags: rails, heroku, cedar

Wasted on Steam - an analytic tool for the Steam platform

Stamped: 09 Jul 2011 | Show comments

Wasted on Steam

I was always curious...

Steam has existed for about 8 years now, and I was always curious how much time and money I've wasted spent with the service. I created a webapp to calculate how many hours have been spent as well as an approximate cost of an account's worth. There are gaps in steam's data, so it's not entirely accurate, but it's a good estimate. Go on, check it out! http://wastedonsteam.com

What's it made out of?

It's built using Rails 3.1, which was the other main motivator for creating this - testing out the new assets pipeline in Rails 3.1. It's hosted on heroku, uses gruff to make simple price graphs, nokogiri to update the data, the money class to store the prices, and delayed_job to push all the updating into the background.

The gemfile is below:

gem 'rails', '3.1.0.rc4'
gem 'sass'
gem 'coffee-script'
gem 'uglifier'
gem 'attempt'
gem 'gruff', :require => false
gem 'rmagick', :require => false
gem 'nokogiri', '1.4.4'
gem 'sprockets', '> 2.0.0.beta.2'
gem 'compass', :git => "https://github.com/chriseppstein/compass.git"
gem 'jquery-rails'
gem 'money'
gem 'steam-prices', :git => "https://github.com/scottkf/steam-prices.git"
gem 'linguistics'
gem 'redcarpet'
gem 'kaminari'
gem 'devise'
gem 'dalli'
gem 'delayed_job'

group :production do
    gem 'hirefire'
end
tags: steam, rails, portfolio

Rails 3.1 — Using ERB/HAML/etc within a Coffeescript JS file

Stamped: 28 May 2011 | Show comments

Need to use some information from your application in your new Coffeescript file? All you have to do is rename it with .erb.coffee or ..coffee instead of .coffee. This lets you do things like

$ ->
    $('#currency_select').change ->
        window.location = window.location.pathname.replace(/^\/(<%=Country.currencies.keys.join '|'%>)|(?!(<%=Country.currencies.keys.join '|'%>))\//i, '/'+$(this + "option:selected").text())

The best part about it is that Rails still knows when to generate the file, it doesn't do it each time! The only thing you can't directly access are the routes (e.g. root_path(), etc), but a gem called js:routes can take care of that for you.

tags: rails, coffeescript, quicktip

Rails 3.1 — 'load_missing_contant': Expected ... to define ... (LoadError)

Stamped: 28 May 2011 | Show comments

Seeing this error?

This is a simple one to fix. I had two Classes of the same name but with different scopes, one was a model and the other a class in a separate module under lib. You have three options in this scenario:

  1. Choose more appropriate names

  2. Move the module out of lib into a gem

  3. Rename the filename of the Class under the lib/ folder, while retaining the same class name.

tags: rails, quicktip

Rails 3.1 — Testing with remote XML/HTML using Nokogiri, stubs, and rspec

Stamped: 19 May 2011 | Show comments

So you're pulling remote data, woo!

I suppose this works in Rails 3.x, but, in order to not be a burden on the server you're grabbing the data from, it would be nice to not have to grab the data each and EVERY time you have to run the unit tests, so you have to mock Nokogiri, but how. It's quite simple, assuming you're using Nokogiri like this: doc = Nokogiri::XML(open("http://tesoriere.com?xml=1")). In your unit test, all you have to do is mock the parse function and you're done!

doc = Nokogiri::XML(open(Rails.root + 'spec/support/tesoriere.xml'))
Nokogiri::XML::Document.stub!(:parse)
Nokogiri::XML::Document.should_receive(:parse).and_return(doc)
tags: rails, bdd, mocks & stubs

Rails 3.1 — use the jQuery Google Library instead of the default

Stamped: 19 May 2011 | Show comments

Rails 3.1 supports all sorts of fun new technologies out of the box, Coffeescript, Sprockets and more. However, the default jQuery 1.6.1 library that is included by default isn't minified, as well as the jQuery-rails library. This results in a whopping 250kb javascript overhead.

To reduce the overhead, and use the Google jQuery library instead, you have to make a couple changes. First, in app/assets/javascripts/application.js:

//= require jquery
//= require jquery_ujs
//= require_tree .

Change this to:

//= require jquery_ujs
//= require_tree .

And in your app/views/layouts/application.html.* change the header by adding an additional line:

<head>
    <title>Steamhours</title>
    <%= stylesheet_link_tag    "application" %>
    <%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
</head>

Note, you can't just add //= require https://../jquery.min.js because sprockets builds the resulting js file each time Rails receives a request (assuming it has changed). It really wouldn't make much sense to download the api and redistribute it, it would defeat the point of even using it in the first place.

All the additions in Rails 3.1 are wonderful, go try them out!

tags: rails, jquery

Rails 3.1 — Fixing the 'ajax:loading' event

Stamped: 19 May 2011 | Show comments

Having difficulty getting the ajax:loading event to trigger? All you have to do is use ajax:beforeSend instead! An example:

Our example coffeescript:

$ ->
    toggleLoading = -> $("#loading").toggle()
    $("#loading").toggle()
    $("form[data-remote]")
        .bind('ajax:beforeSend', toggleLoading)
        .bind('ajax:complete', toggleLoading)
        .bind('ajax:success', (event, data, status, xhr) ->
            $("#list").html($.parseJSON(data))
        )
        .bind('ajax:error', (xhr, status, error) ->
        )

Our form:

<%= form_for @profile, :id => "hours_form", :url => {:action => :show }, :remote => true do |f| %>
    
    <%= field_set_tag do %>
        <%= f.label :profile_name %>
        <%= f.text_field :profile_name %>
    <% end %>
    
    <%= submit_tag 'Submit', :id => "submit" %>
    
<%end%>
<div id="loading">
Loading...
</div>

And finally, the controller:

class ProfilesController < ApplicationController

  def index
    @profile = Profile.new
  end
  
  def show
    @profile = Profile.find(p.id, :include => :games)
    
    respond_to do |format|
      format.html
      format.js { render :json => @profile }
    end
  end
end
tags: rails, ajax, jquery

When should I use Ruby on Rails, or something else? (Comparing Symphony and Rails)

Stamped: 18 Oct 2010 | Show comments

When beginning a new project, the first thing I come across is what software/language/methodology do I use to complete the project? The answer is quite simple, it depends.

Oh, you clever chap, what a cheeky answer

The truth of that matter is, it does depend solely on your application requirements and your knowledge of what's available. Nearly 90% of the time in my situation, it either comes down to either of two things:

The other 10% of the time I use my own solution, as in SAMOS, but that's for another day.

Both Rails and Symphony subscribe to a Model-View-Controller (MVC) architectural software design methodology. Rails treats the methodology quite literally, while Symphony does so a little more loosely. The nomenclature in each are as follows:

RailsSymphony
ModelModelSection
ViewViewPages
ControllerControllerData-Sources/Pages/Components

I won't really describe rails much, because there's plenty of information about it. A great place to start is here: http://edgeguides.rubyonrails.org/getting_started.html.

Then I'll go somewhere else!

However, Symphony could use some explanation. Symphony's models are called sections (which contain many fields, just like fields in a database), and they are just like rails models. It's views are controlled by pages, and it's controllers are controlled (hah!) by data-sources, pages, and components. While that may be a little confusing, it works quite wonderfully.

Pages control what gets output as HTML, but you can also include logic in them, just like Rails does with ERB/HAML. However, a best practice is to stub out a lot of the logic into components (templates) to make it more DRY.

Data-sources are the link between the pages/components and the sections/fields. In rails, this would be the ActiveRecord interface. They grab data in a certain way based on lots of different things in lots of different ways. I couldn't be more vague, I know.

Components are similar to helpers/partials in rails, where you can lump similar things together so you can reuse them.

Go on then, answer the original question

How I decide which to use basically involves me looking at this table and deciding how I feel like torturing myself for that certain project. Most of the time the biggest issues are whether or not it contains a lot of logic that needs to be tested, and which template language I want to use.

'
RailsSymphony
Testable?Yes, I use BDDNot so much
Template LanguageERB/HAMLXSLT (this is quite amazing)
GUINopeYes, you can see an example here
OutputHTML/XML/JSON/etcHTML (as xml), JSON
DatabaseEverything you can throw at itMySQL
Hosted at Godaddy?FMLDIAF

There are a couple other things to compare, but these are often the most important.

Great! Losing patience, how do you decide?

If it's more of a web application and not as much as website, I often use rails, because they usually involve logic that should be tested.

The template language shouldn't be, but is a huge issue to a shallow individual such as myself. You can do exactly the same stuff in both ERB/HAML and XSLT. They both have their advantages and disadvantages. ERB/HAML's main pro going for is that it's easy to use and quite succinct. XSLT's plus is that it's XSLT and lets you traverse data (XML) in such an amazing way, but it comes at price, it's quite verbose and ancient (it's from 1999). For me? It depends, again, and how bored I am with one over the other.

Whether or not I need Symphony's GUI is also another important issue. If I don't feel like building an interface and using the default Symphony UI for tech-savvy clients I will often choose this (though you can also make your own interface as well).

Once again, if it's more of web app, I'll often use rails because I might need an API (where I might need to output XML/JSON) right away, or at some point in the future. In Symphony, you can output XML right out of the box, and can create a REST API, but the CUD part of CRUD is a little harder to manage in symphony this way.

The database usually isn't as huge issue, and mainly depends on where the app/site is being hosted. Symphony currently only supports MySQL, but nearly every host supports MySQL, so whatever!

And there you have it, it simply depends, on... lots of stuff. </shill>

tags: rails, symphony, development

Rails 3.x — Self-referential has_many :through Parent<->Child relationship modelling

Stamped: 01 Sep 2010 | Show comments

Seriously?

Srsly, that's more of an SEO title than anything, because damnit, I know how the hell it works at an SQL level and it was frustrating to get working in Rails because of syntax, namely :class_name => :object, when it should be :class_name => "object". I hope someone finds this useful, because when it comes to rails 3 there's not too much stuff that's recent.

Get to the point, show me the goods

rails new confusingTitleProject
cd confusingTitleProject
rails g model Person name:string
rails g model PersonRelationship parent_id:integer child_id:integer
#App/Models/PersonRelationship.rb
class PersonRelationship < ActiveRecord::Base
  belongs_to :parent, :class_name => "Person"
  belongs_to :child, :class_name => "Person"
end

#App/Models/Person.rb
class Person < ActiveRecord::Base
  
  validates_presence_of :first_name, :last_name

  has_many     :parent_child_relationships,
               :class_name            => "PersonRelationship",
               :foreign_key           => :child_id,
               :dependent             => :destroy
  has_many     :parents,
               :through               => :parent_child_relationships,
               :source                => :parent

  has_many     :child_parent_relationships,
               :class_name            => "PersonRelationship",
               :foreign_key           => :parent_id,
               :dependent             => :destroy
  has_many     :children,
               :through               => :child_parent_relationships,
               :source                => :child
end

Then, as an example, you can do the following to add a parent:

a = Person.new; b = Person.new
b.parents = [a]
tags: rails, self join, has_many through, habtm
recent entries
Rails — A faster way for next and previous links on a post, article, or any model
The awkward things Siri says
Node.js — Getting oAuth Up and Running Using Express.js and Mongoose
Node.js — Getting oAuth Up and Running Using Express.js, Railway.js and Mongoose
Migrating from Rails 3.1 RC4 to RC5 using Heroku's Cedar Stack (also compass, unicorn, and sendgrid)
Random Freeze Fix for GTX 460 in 10.6 (osx86)
Wasted on Steam - an analytic tool for the Steam platform
Rails 3.1 — SQL logging to STDOUT during testing (with rspec, test::unit, or cucumber)
Rails 3.1 — Using ERB/HAML/etc within a Coffeescript JS file
Rails 3.1 — 'load_missing_contant': Expected ... to define ... (LoadError)
View the entire archive of articles