Tropical Software Observations

Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts
01 June 2011

Posted by Anonymous

at 10:19 AM

324 comments

Labels:

Using RABL in Rails JSON Web API

Let's use an event management app as the example.

The app has a simple feature: a user can add some events, then invite other users to attend the event. Its data are represented in 3 models: User, Event, and Event Guest.



Let's say we are going to add a read-only JSON web API to allow clients to browse data records.

Problems

Model is not view

When working on a non-trivial web API, you will soon realize that models often cannot be serialized directly in a web API.

Within the same app, one API may need to render a summary view of the model, while another needs a detail view of the same model. You want to serialize a view or view object, not a model.

RABL (Ruby API Builder Language) gem is designed for this purpose.

Define once, reuse everywhere

Let's say we need to render these user attributes: id, username, email, display_name, but not password.

In RABL, we can define the attribute whitelist in a RABL template.

# tryrabl/app/views/users/base.rabl
attributes :id, :username, :email, :display_name

To show an individual user, we can now reuse the template through RABL extends.
# tryrabl/app/views/users/show.rabl
extends "users/base"
object @user

## JSON output:
# {
#     "user": {
#         "id": 8,
#         "username": "blaise",
#         "email": "matteo@wilkinsonhuel.name",
#         "display_name": "Ms. Noe Lowe"
#     }
# }

Here's another example to show a list of users.
# tryrabl/app/views/users/index.rabl
extends "users/base"
collection @users

## JSON output:
# [{
#     "user": {
#         "id": 1,
#         "username": "alanna",
#         "email": "rubie@hayes.name",
#         "display_name": "Mrs. Gaylord Hoeger"
#     }
# }, {
#     "user": {
#         "id": 2,
#         "username": "jarrell.robel",
#         "email": "jarod@eichmann.com",
#         "display_name": "Oran Lebsack"
#     }
# }]

The template can be reused in nested children as well, through RABL child.
attributes :id, :title, :description, :start, :end, :location
child :creator => :creator do
extends 'users/base'
end

## JSON output:
# {
#     "event": {
#         "id": 7,
#         "title": "Et earum sed fuga.",
#         "description": "Quis sed ..e ad.",
#         "start": "2011-05-31T08:31:45Z",
#         "end": "2011-06-01T08:31:45Z",
#         "location": "Saul Tunnel",
#         "creator": {
#             "id": 1,
#             "username": "alanna",
#             "email": "rubie@hayes.name",
#             "display_name": "Mrs. Gaylord Hoeger"
#         }
#     }
# }

Join table rendered as subclass

I notice a recurring pattern in two recent projects. For instance, in this example, from the client's point of view, Event Guest is basically a User with an additional attribute: RSVP status.

When querying the database, usually we need to query the join table: event_guests.
class GuestsController < ApplicationController
def index
@guests = EventGuest.where(:event_id => params[:event_id])
end
end

But when rendering, the result set needs to be rendered as a list of Users. RABL allows you to do that easily, using its glue feature (a weird name though :).
# tryrabl/app/views/guests/index.rabl
collection @event_guests

# include the additional attribute
attributes :rsvp

# add child attributes to parent model
glue :user do
extends "users/base"
end

## JSON output:
# [{
#     "event_guest": {
#         "rsvp": "PENDING",
#         "id": 3,
#         "username": "myrna_harvey",
#         "email": "shad.armstrong@littelpouros.name",
#         "display_name": "Savion Balistreri"
#     }
# }, {
#     "event_guest": {
#         "rsvp": "PENDING",
#         "id": 4,
#         "username": "adelle.nader",
#         "email": "brendon.howe@cormiergrady.info",
#         "display_name": "Edgardo Dickens"
#     }
# }]

The complete Rails example code is available at github.com/teohm/tryrabl.

25 May 2011

Posted by Anonymous

at 12:27 PM

1 comments

Labels: ,

Using JQuery Validation in Rails Remote Form

In a recent project, I was trying to use JQuery Validation in an earlier version of Rails 3 remote form (jquery-ujs). They didn't work out well in IE.

After experimenting with the latest jquery-ujs and making an embarrassing mistake, it turns out that the issue is resolved in the latest version.

(Mistake: You may notice I removed a previous post about this topic, where I mistakenly concluded the latest jquery-ujs is not working with JQuery Validation. Thanks to JangoSteve for pointing it out. The post was misleading, so I believe it's best to remove it to avoid confusion. :-)

Get the latest jquery-ujs

There are 2 reasons to use the latest jquery-ujs:

  1. it has a patch that fixes the issue (see issue #118).
  2. it exposes an internal function that we may need -- $.rails.handleRemote() (see more details)

Working example

The example is tested with:

Try run the example page on IE7/8: /quirks/jquery-validate-ujs-conflict/jquery-ujs-latest.html

When using submitHandler in JQuery Validation

If you are using JQuery Validation's submitHandler(form) function, you need to call $.rails.handleRemote( $(form) ) function manually, so that it submits the form via XHR.

$('#submit_form').validate({
submitHandler: function(form) {
// .. do something before submit ..
$.rails.handleRemote( $(form) ); // submit via xhr

// don't use, it submits the form directly
//form.submit();

}
});

27 August 2010

Posted by Isak Rickyanto

at 12:37 PM

5 comments

Labels: , , ,

Initial Thoughts on Padrino, a Fast Ruby Web Framework Based On Sinatra

In search of a Rails alternative in the Ruby world, I found a popular and simple web framework, Sinatra, that was introduced to me by a friend. It is very easy to get started with Sinatra, especially for a simple Hello World kind of web app.

But based on my experience with Rails, Grails, and CakePHP, all web frameworks that help us develop easier and faster with their features, helpers, and also lots of plugins, I admit that I was not that interested in using Sinatra in a real project.

I like Sinatra's simple and minimal concepts, but in the real world we sometimes need more than that. So, although I believe I can build any kind of web application with Sinatra, I feel that sooner or later I will look for helpers, utilities, or plugins that can help me in development.

Then I found Padrino, also introduced to me by my friend, which is a bigger and richer framework than Sinatra in term of features and helpers. It seems like a complete framework but still sticks to Sinatra's core concepts and philosophy, since the developer built this framework as a layer on top of Sinatra while trying to keep it fun and easy. You can find Padrino here: www.padrinorb.com

The things I like best about Padrino:

1. Code Generator - easily generates beautiful admin interfaces with built in authentication.
2. Agnostic Way - gives us options to choose what module or library we want to use, like letting us choose between Active Record, Datamapper, or Sequel for ORM.
3. Fast Performance - Padrino's performance benchmark shows us it is faster than Sinatra itself, which sounds impossible right?

Benchmarks for Padrino vs Merb vs Rails vs Sinatra vs Ramaze:

# Rendering a string inline
Merb 1.1.0 => 1749.97 rps
Padrino 0.9.10 => 1629.15 rps
Sinatra 1.0.0 => 1537.78 rps
Rails 3.beta3 => 381.76 rps
Ramaze 2010.04.04 => 270.08 rps

# Rendering a basic erb template
Merb 1.1.0 => 1490.8 rps
Padrino 0.9.10 => 1416.84 rps
Sinatra 1.0.0 => 1157.89 rps
Rails 3.0.beta3 => 330.58 rps
Ramaze 2010.04.04 => 254.23 rps

# Rendering a simulated simple app
Padrino 0.9.10 => 675.79 rps
Sinatra 1.0.0 => 652.0 rps
Merb 1.1.0 => 642.29 rps
Rails 3.0.beta3 => 201.86 rps
Ramaze 2010.04.04 => 130.62 rps

(copied from http://www.padrinorb.com/blog/padrino-0-9-10-released-built-for-speed)
Get the source code for benchmark from http://github.com/DAddYE/web-frameworks-benchmark

4. Comprehensive Features - Padrino provides us with a lot of features, helpers and libraries that aid us in web development

5. Great Support for Shiny Things like HAML, SAAS, MongoDB

Along with these advantages, I also found some things that need to be improved:

1. Small Community - I hope it will get better in the future (Padrino's Google group only has 122 members). Perhaps it needs more “marketing” to attract newcomers. Open source projects gain trust more easily if they have large and active communities.

2. User Guides - Padrino needs more comprehensive and complete tutorials. For example, if a new user comes to the Padrino site without existing Active Record, Datamapper, or other compatible ORM experience, I believe they will be confused because the documentation does not include those ORMs. I only found a simple active record example in a blog tutorial. I admit there are many external examples covering documentation of these ORMs, but the information is not directly linked/integrated with Padrino. From my experience, I ran into difficulty when trying to build an HTML form with HABTM relation, and it was difficult to find any relevant information about the problem as related to Padrino. Fortunately I had experience with Active Record so finally after struggling for a few hours I found a solution but this might not be the case with someone new to web development and experiencing it for the first time using Padrino.

3. Sample Project, Real Live Projects still rare
In learning, it is easier for me as newcomer to learn from examples so it will be great to see a few sample projects, something more than just a simple blog app. Also I still can't find a list of real live production sites or projects using Padrino. I found only 1-2 sites that use Padrino (1 shopping project and watched.it). There may be many more than that number who already use Padrino, but this information is not well documented. If you are interested, here is a discussion about who is already using Padrino:
http://groups.google.com/group/padrino/browse_thread/thread/d439332aa95227e3/4265f4a2cad796c7

Conclusion:
I believe that if the people behind Padrino keep doing a good job with developing and promoting Padrino, and also take care their community, Padrino will become a great web framework, maybe a better alternative than Rails. If you have experience with Rails and Sinatra, it will a be friendly framework for you to work with.

I would confidently use Padrino for simple applications but personally I feel it will be risky to build a serious application using an open source project without knowing there is more significant community support behind it (please note: I am not saying the Padrino community is not welcoming or responsive).

26 January 2010

Posted by Kamal Fariz

at 6:16 PM

1 comments

Labels: , , , ,

Full-text search in Rails with Sunspot

There comes a time in every app when doing a “SQL LIKE” query just doesn’t cut it. I’m going to show you how easy it is to add proper full-text search to your Rails app using the Sunspot::Rails plugin.

Sunspot

Sunspot is a standalone Ruby library that makes integrating with a Solr search engine a cinch. It wraps all the nitty gritty of indexing and querying in a declarative DSL which you can use to expose virtually any Ruby object to be searched, not just ActiveRecord models. The sunspot gem bundles a standalone Solr search engine (mostly stock, served by Jetty, although also contains support for geolocational ordering).

Sunspot::Rails is Rails plugin which is basically Sunspot the library plus some hooks into ActiveRecord to update indexes on creates and updates as well as the Rails request lifecycle commit the index at the end of every request. It adds the DSL as class methods into ActiveRecord to allow you to configure the index much like in the style of configuring association or named_scopes. The gem also bundles a set of rake tasks to manage starting, stopping and restarting the Solr service.

Installation

  1. Install the gem
    $ gem install sunspot_rails
  2. Edit your config/environment.rb to include
    config.gem 'sunspot', :lib => 'sunspot'
    config.gem 'sunspot_rails', :lib => 'sunspot/rails'
  3. Generate the sunspot configuration file in config/sunspot.yml
    $ ./script/generate sunspot
  4. Run the Solr service
    $ rake sunspot:solr:start
    (if Rake complains that it couldn’t find this task, add require 'sunspot/rails/tasks' to the top of your Rakefile).

Defining an Index

The first thing you need to do before anything can be searched is creating an index. There is two parts to this.

The first part is defining an index. For Rails models, you can define it using the searchable class method. Suppose we have an Article that belongs to an Author.
class Article < ActiveRecord::Base
belongs_to :author

searchable do
text :title, :boost => 2.0
text :body
text :author_name do
author.name
end
time :updated_at # for sorting by recent
string :sort_title do # for sorting by title, ignoring leading A/An/The
title.downcase.gsub(/^(an?|the)/, '')
end
boolean :published, :using => :published?
end

def published?
state == :published
end
end
Sunspot supports text, string, time, boolean, integer and float fields. When planning what to index, note that only text fields are exposed as full-text search while the other field types are used for restricting, sorting and faceting.

What I like about the DSL is the flexibility. You can directly index an ActiveRecord attribute (:title, :body) or virtual attributes by giving it a block (:sort_title) or a symbol to a method (:published). Even indexing associations is really a matter of calling methods on it.

The second part is indexing. Sunspot provides a utility method to reindex all records for a particular class. In our example, we can call

Article.reindex!

and have the entire Article index rebuilt. For finer grained indexing, you can call Article#index! on a particular instance. As mentioned above, if you are creating and updating models via controllers as in a typical Rails app, this should all be transparent to you.

Querying

Sunspot provides a flexible DSL for querying. A SearchController might look something like this
class SearchController < ApplicationController
def show
@search = search(params)
end

protected
def search(options)
Sunspot.search(Article) do
keywords options[:query]
with(:published, true)
order_by :updated_at, :desc
paginate :page => options[:page]
end
end
end
keywords will be applied to all text fields. The remaining non-text fields can be defined to restrict the query (in the example, we want restrict it to published Articles) and ordering (in the example, we ordered by updated_at). If you don’t define an ordering, the results will be returned sorted by relevance based on occurence and location of the keywords in the document and the index as a whole. You can tweak the relevance score by defining boosts — in this example, Article titles that match the keywords are given a boost over other Articles that may match the keyword elsewhere.

You can define multiple restrictions and they don’t always have to be for equality. It supports restricting by a value being less-than, greater-than, between, any or all (when comparing for an indexed array). The restrictor with(:published, true) is simply a short-hand for with(:published).equal_to(true). You can also test the absense of a value using the without operator.

Finally, Sunspot plays nice with the WillPaginate plugin. In your view, you can paginate easily by doing
<%= will_paginate @search.results %>
and expect it to work seamlessly.

Conclusion

That’s all there is to it to get up and running with Sunspot. My take-home point is Sunspot exposes extremely flexible DSLs that allow you to scale from simple to pretty complicated queries with ease.

If this interested you, you may want to check out the wiki for other features not covered by this article including highlighting of keywords, facets and stored fields.

27 August 2009

Posted by Unknown

at 1:29 PM

0 comments

Labels: , , , ,

Install Apache-Passenger (mod_rails) on Ubuntu 9.04 in 10 steps

A very quick guide on installing Passenger (mod_rails) on a clean install of Ubuntu jaunty 9.04.

  1. apt-get install build-essentials
  2. apt-get ruby apt-get ruby-dev
  3. gem install rubygems-update
  4. cd /var/lib/gems/1.8/rubygems-update-1.3.x/
  5. ruby setup.rb or bin/update_rubygems
  6. gem update --system
  7. gem update
  8. gem install passenger
  9. apt-get install libopenssl-ruby1.8 apache2-prefork-dev libaprutil1 libaprutil1-dev
  10. passenger-install-apache2-module
At the end of the installation, you will be prompted to add a few lines to your apache conf -- copy and paste the lines to /etc/apache2/apache2.conf and you are ready to deploy Rails apps immediately.

For the rest of the setup and configuration see here. Hope this helps someone out there.