Showing posts with label rails3. Show all posts
Showing posts with label rails3. Show all posts

Saturday, 1 October 2011

Using the html5 boilerplate and rails 3

I am experimenting with using the html5 boilerplate with rails 3.0x. It is an interesting setup. I am using Russ Frisch's html5 boilerplate template, but updating it to use the latest boilerplate code.
It seems to be a pretty nice way of getting your css and js ducks in a row. Over the next few days I need to see how it handles having jquery mobile in the mix. I am also not 100% sure how to use a some sort of grid layout template (like 960 or 114opx) with it. Or even if you should!
We shall see.

Wednesday, 28 September 2011

Suffering Rails3 slowdown

We've just pushed our Rails 3 upgraded app to production... and are suffering a massive slowdown in insert/update speed over Rails 2.
At the moment I am not sure of the exact cause of this.

It *might* be mysql inserts, though I can't quite see why that would be.

It *might* be because this new version we are using vestal_versions to track changes.

It might be because the moon is in the house of Mars for all I know!

I hate getting stung by unknowns. The speed on our test environment is tolerable, slightly slower than the rails 2 version, but I was willing to accept that because the new version is doing so much more.

Bench-marking is one thing.. but know why the bench marks are slower is the key!

Thursday, 21 July 2011

meta_search sort_link helper and associations

It took me a while to find this, so, for my own memory I am going to quickly write this up.

I have a view that shows a table of objects (a pretty standard index view). The only issue was I want to sort on one of the columns that actually has data coming not from the main object but an association.
What I discovered is a line in a posting here that says:

You can define your own custom sort scopes. Define scopes named “sort_by__asc” and “sort_by__desc” and sort_link @search, :name will work as you might expect.

So, I have an object of 'info' defined like this:
class Info < ActiveRecord::Base
belongs_to :region
scope :contains_string, lambda {|str| where(:name.matches % "%#{str}%")}
search_methods :contains_string

scope :active, lambda{where(:active => true)}
search_methods :active, :type => :boolean
end


What I found is that you can put this at the end:
scope :sort_by_region_name, lambda{joins(:region).order("regions.name asc")}
search_methods :sort_by_region_name


and then in my view I can do:

<table>
<thead>
<tr>
<td width="25%"><%= sort_link @info_search, :name, "Info" %></td>
<td width="25%"><%= sort_link @info_search, :region_name, "Region" %></td>
<td><%= sort_link @info_search, :active %></td>
</tr>
</thead>
...body info...
</table


And in my controller:
class Admin::InfosController < Admin::BaseController
def index
search = params[:search] || {"meta_sort" => "name.asc"}

@info_search = Info.search(search)
@infos = @linfo_search.paginate(:page => params[:page]||1, :per_page => 15) # load all matching records
end
end


And presto I have an index that
a) default to sorting by name (see the first line of the index method)
b) let's me sort on an associated value

Cool!

Monday, 28 March 2011

Toygaroo on Rails

Seeing as there has been a lot of press recently about my company Toygaroo I thought I might throw out some tech info for those of you who care.
For those who don't know, Toygaroo is America's biggest toy rental company (think netflix for kid's toys). Recently - March 25th, 2011 - we appeared on the season premiere of Shark Tank - a national television show on ABC. About 4.6 million people watched the show.

The Platform
Toygaroo is a Ruby on Rails 3 application. It is based heavily on the code we wrote for FilmAmora.com, Spain's leading DVD rental company. We are running on Ubuntu 10.04 LTS. We're using Passenger 3 and Nginx (doesn't everyone?!). We're using Dalli in front of MemCached (though we've had some issues with this that I really should blog about!).

The Host
Right now we are running on Amazon EC2 service - though with Mark Cuban coming on board that might change. I am a big fan of EC2, though I think the machines are a little underpowered for what you pay.

The numbers!
In the 2 hours after the show aired we received around 70,000 page views. The basic architecture is a load balancer sitting in front of a whack of app servers. We are not using a scaling solution right now - hey, we're a start up! - so we wind up more servers if we feel we need them. It is a pretty simple process - I have a script for us to follow to get an Ubuntu server up and running in no time.

Caching
I looked into other solutions, like Varnish, but decided that Rails could handle the job with a combination of page, action and fragment caching. And I haven't been wrong so far. Even under heavy load we are getting great response time. The key - as I found out with FilmAmora - is what level to cache on. We cache 'blocks'. i.e. if you look at an index page with lots of toys we cache each toy block. That block can appear on many different pages, so it is a nice solution I find.

As time goes on I'd like to post more about how Toygaroo is coded and running as I think it will provide a nice real world example for what you can do with Rails 3. If you have any questions drop me a line (comment on here).

Wednesday, 24 November 2010

will_paginate and ajax in rails 3

After googling and stackoverflowing around I couldn't find anything that explicitly said how do to this.

I have a page that has 'pagable' areas on it, and want to load these using ajax. And I am in rails 3!

I found it actually was quite easy and only needed a few lines of code.

1 - In the page view (users/show.html.erb)
To enable the data-remote attribute on will_paginate's links

<div id="queue"><%= render :partial => 'queue' %></div>
<script>
$(document).ready(function() {
$('.pagination a').attr('data-remote', 'true');
});</script>



2 - In the controller (users_controller.rb)
To allow a js response


def show
@user = User.find(params[:id])
@queue_items = @user.queue_items.with_state(:pending).paginate(:page => params[:queue_page] || 1, :per_page => 1)
respond_to do |format|
format.html
format.js
end
end



3 - A show.js.erb template
You need the second line to reapply the data-remote to the new links
$('#queue').html('<%=escape_javascript render :partial => "queue" %>');
$('.pagination a').attr('data-remote', 'true');




That's it! Your new dataset should load into the 'queue' div.

Monday, 18 October 2010

Customizing Devise to a pseudo multi-stage Signup

The Requirements!
On our new site we want to have a kind of multi-stage signup. The first page the user chooses a type of subscription, the second page they create their account, with address information, and the third page they enter their billing info. If they don't do the third stage that's ok as we will warn them that their account is incomplete (and they won't get any services until they give us the billing info)

We are doing this new site in Rails3 and I thought I'd use Devise as the authentication engine. In our old site we use Restful Authentication, which has been awesome. But Devise seems to be the thing all the kids are into today!

So here's how we did it:

Step 1:
Create a New Controller
We created a new controller to handle the first stage of the signup. It's very simple. It only is a "new" method (for now) as we do no saving or updating of the subscription.

rails g controller subscriptions new

create app/controllers/subscriptions_controller.rb
route get "subscriptions/new"
invoke erb
create app/views/subscriptions
create app/views/subscriptions/new.html.erb
invoke rspec
create spec/controllers/subscriptions_controller_spec.rb
create spec/views/subscriptions
create spec/views/subscriptions/new.html.erb_spec.rb
invoke helper
create app/helpers/subscriptions_helper.rb
invoke rspec
create spec/helpers/subscriptions_helper_spec.rb



The controller looks like this:
class SubscriptionsController < Devise::RegistrationsController
def new
@subscription_plans = SubscriptionPlan.visible
end
end


Pretty basic stuff!

The view is:
<% title "Sign up" %>

<div>Pick a subscription</div>
<% semantic_form_for :subscription, :url => users_sign_up2_path, :html => { :method => :get } do |form| %>
<%= form.input :plan_id, :as => :radio, :collection => @subscription_plans.map{|sp| ["#{sp.description}", sp.id]} %>
<% form.buttons do %>
<%= form.commit_button "Continue" %>
<% end %>
<% end %>


Step 2:
Routes!
We needed to add some custom routes to the users block to let Devise know what was going on:
  devise_for :users do
get "/users/sign_up" => "subscriptions#new"
get "/users/sign_up2" => "registrations#new"
end


The first line says use the subscriptions controller as the first stage of the sign up. That way we don't need to change any helpers and can do things in the standard Devise way. The second adds the second stage - the actual user new-ing and creation:

Step 3:
Override the Registrations Controller.
This is where 'the good stuff' happens! Note that we don't need to override the create method. Devise has a hook in it for building the model, so we override that instead.
It should be noted that this only works if the model can't be saved - i.e. your validations are complete. In our case we are creating 3 models in 1: subscription, address and user. The subscription and address will get saved if the user gets saved. So, we have validations in the user requiring a subscription and address. Then validations on the address to make sure it is good. That way if the validation of the address fails the validation of the user fails and we get chucked back out the the new user form.
The code might say it better!

class RegistrationsController < Devise::RegistrationsController
def new
@address = Address.new
begin
@subscription_plan = SubscriptionPlan.find(params[:subscription][:plan_id])
rescue Exception => e
redirect_to users_sign_up_path and return
end
@user = User.new
end

protected

# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
address_info = params[:user].delete(:address) rescue {}
begin
sub_info = params[:user].delete(:subscription_plan)
@subscription_plan = SubscriptionPlan.find sub_info["id"]
rescue Exception => e
redirect_to users_sign_up_path and return
end

subscription = Subscription.new(:subscription_plan_id => @subscription_plan.id)

@address = Address.new(address_info.merge(:country => "US"))

@user = User.new(params[:user].merge(:first_name => @address.first_name, :last_name => @address.last_name))
@user.subscription = subscription
@user.current_shipping_address = @address
end

def after_sign_up_path_for(resource)
new_user_billing_detail_path(resource)
end

end


Also note that we override 'after_sign_up_path_for'. That way we move the user onto the third stage of the process - the billing info - if they sign up.

This is still in early stages, and not yet live, but the process seems to work.