JasonDaly.name

PHP, Ruby, Symfony, Rails, Doctrine, MooTools. Web Development.

Posts tagged with "ruby"

October 12, 2011

Decorating Models from the View Layer with Draper

Draper is a great gem allowing for the Decorator pattern to easily be applied to ActiveRecord models.

When integrating Draper into an existing application, a decorator or collection of decorators must be retrieved/built instead of ActiveRecord models in order to use the functionality of your decorators (the decorators include all functionality of the ActiveRecord model they’re decorating). Draper’s README explains three methods for doing this from within the controller:

  • Call .new and pass in the object to be wrapped

    ArticleDecorator.new(Article.find(params[:id]))
    
  • Call .decorate and pass in an object or collection of objects to be wrapped:

    ArticleDecorator.decorate(Article.first) # Returns one instance of ArticleDecorator
    ArticleDecorator.decorate(Article.all)   # Returns an array of ArticleDecorator instances
    
  • Call .find to do automatically do a lookup on the decorates class:

    ArticleDecorator.find(1)
    

I’d like to decorate ActiveRecord models from the view directly, leaving the controllers unchanged (directly fetching the record(s) through ActiveRecord). I use the following decorate method in app/helpers/application_helper.rb.

def decorate(records)
  collection = [records] if !objects.is_a? Array
  return records unless collection.first.class.name.match(/Decorator$/).nil?
  klass = (collection.first.class.name + 'Decorator').constantize

  decorated_objects = []
  collection.each do |object|
    decorated_records.push(klass.decorate(object))
  end

  return decorated_records if records.is_a? Array
  decorated_records.first
end

Now I can simply wrap any record or collection with decorate in any view to leverage the decorator functionality.

15 notes Tags: ruby draper gem code rails ruby on rails tips decorator helpers

October 7, 2011

Dynamically Building Squeel DSL Blocks

Given a partial query such as Jas D I recently needed to search for matches in a users table across multiple fields:

  • Email (:email)
  • Username (:username)
  • First Name (:first_name)
  • Last Name (:last_name)

I also wanted this same search to be reusable; flexible enough to let me specify other columns within the User model to search by, such as just the first_name and last_name fields.

The requirements were simple:

  • Every ‘chunk’ (jas, d) must be found within 1+ fields for a match to be successful
  • Stop words and undesireable characters should be parsed out ahead of time

Squeel is an awesome gem by Ernie that provides a nice DSL for ActiveRecord. The closest example in the docs to meet my requirements is through the user of the like_any predicate method.

User.where{(first_name.like_any words) | (last_name.like_any words) | (email.like_any words) | (username.like_any words)}

There are two issues with this:

  1. This doesn’t generate the SQL I want; the above DSL block will match if any word matches any field.
  2. This isn’t dynamic; I can’t specify the columns to search dynamically through the DSL.

I had initially hoped there was some combination of parentheses, |’s and &’s I could use to generate a query that met my requirements, but quickly realized I needed to work with the internals of Squeel directly (I could not see a way to specify colum names dynamcially through the provided DSL).

Working with Squeel Internals

The method must be a class method

def self.live_search(words, columns)
  # …
end

To meet my requirements, the method must

  1. Loop over each word
    1. Loop over each column
    2. Build a Squeel Node attempting to match the word against each column
  2. Join each expression with a boolean OR (|) from the DSL
  3. Join each group of OR‘d expressions with a boolean AND (&)
  4. Pass the constructed Squeel DSL block to where{}
  5. Return

Or in code

def self.live_search(words, columns)
  words.map do |word|
    columns.map do |column|
      Squeel::Nodes::Predicate.new(Squeel::Nodes::Stub.new(column), :matches, word)
    end.inject do |t, expr|
      t | expr
    end
  end.inject do |t, expr|
    t & expr
  end.tap do |block|
    return where{block}
  end
end

Note: The use of the loops, |, & and :matches predicate method are specific to my requirements. This should provide enough of a guide though to build a dynamic Squeel DSL block yourself based on your own requirements.

The above method in the User model can be used from a controller as follows

query       = params[:query].downcase
stop_words  = ['mr', 'mrs', 'ms', 'miss']
# Allows apostrophe for last names like O'Donnel
query_words = (query.downcase.split(/[^\w\']+/) - stop_words).map{ |w| "%#{w}%" }

results = User.active.live_search(query_words, [:first_name, :last_name, :email, :username])

and in other instances of the live-search where :email or :username are irrelevant

results = User.active.live_search(query_words, [:first_name, :last_name])

47 notes Tags: ruby rails squeel dsl api code active_record

August 23, 2011

Ruby Capitalization Obeying English Title Grammar

Strings passed will be formatted to obey English title rules.

"HAPPY BIRTHDAY".proper_titleize #=> Happy Birthday
"OvEr THE MouNTaiN And ThrOuGH thE WooDS".proper_titleize #=> Over the Mountain and Through the Woods

A link to the gist can be found below the source.

class String
  # Converts a string into a properly capitalized English title
  #
  # @param [String] string the string to titleize
  #
  # @return [String] the titleized string
  def proper_titleize
    words = self.downcase.split(/\s+/) 
    combination_exceptions = [
                              'even though', 'so that', 'even if', # subordinating conjunctions
                              'to do', 'to be' # infinitives
                             ]
    exceptions = %w( because if after when although while since ) # subordinating conjunctions
    exceptions += %w( the a an ) # articles
    exceptions += %w( to do ) # infinitives
    exceptions += %w( nor but or yet so both and either neither not
                      of in is for on with as by at from
                      amid anti as down into like minus near off
                      onto over past per plus save than up upon via ) # adposition

    title = []
    i = 0

    while i < words.length do
      if i == 0 or i == words.length-1 or words[i].upcase === words[i]
        title.push(words[i].capitalize)
      elsif combination_exceptions.include?("%s %s" % [words[i], words[i+1]])
        title += [words[i], words[i+1]]
        i += 1
      elsif !exceptions.include?(words[i])
        title.push(words[i].capitalize)
      else 
        title.push(words[i])
      end

      i += 1
    end

    return title.join(' ').strip
  end
end

(Source: gist.github.com)

128 notes Tags: grammar code ruby gist function helper string string manipulation

August 9, 2011

Spelling Suggestions from Google in Ruby

Google used to offer a SOAP API for spelling suggestion/correction but put it out of service in November 2010. Since then the only way I had found to reliably get Google’s recommended spelling suggestion for an incorrectly spelled phrase was through the same interface their toolbar browser extension uses to help correct spelling mistakes. My SpellCheck project is a PHP5 tool that asks Google it’s suggestion for a given phrase the same way their own toolbar does and based on the returned response, the original phrase will be parsed and updated to reflect the recommended changes.

How the Google Toolbar Used to Make Replacements

The Google toolbar sends and HTTP POST request to https://www.google.com/tbproxy/spell?lang=en&hl=en originally containing an XML body as shown below (Note: appls and ornages is the phrase being queried).

<?xml version="1.0" encoding="utf-8" ?>  
  <spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">  
    <text>appls and ornages</text>  
  </spellrequest>

The returned response would come as XML too, with a body something like

<?xml version="1.0" encoding="utf-8" ?>
  <suggestions>
    <c o="0" l="5">apples\tapple\tapps</c>
    <c o="10" l="7">oranges\torange</c>
  </suggestions>

Each c node contains an o attribute which is the starting point of a word to be replaced and an l attribute which is the length of the original word to be replaced. The text content of each c node is a tab-delimited list of suggestions in order of it’s potential to be what you really meant to type.

Google’s HTTP responses no longer contain o or l attributes, suggesting their toolbar does a bit more work to determine where replacements should be made based on the suggestions returned.

Moving to Ruby and Another Solution

Rewriting an application in Ruby I needed to find a different way to get suggestions from Google. Using Nokogiri’s CSS selector support, This proved to be trivial.

require 'open-uri'
require 'nokogiri'
require 'awesome_print'

query = CGI::escape(ARGV)
doc = Nokogiri::HTML(open("http://www.google.com/search?q=#{query}"))
nodes = doc.css('#topstuff p a')

ap nodes[0].content if nodes.length > 0

(Note: You can put this in spellsuggest.rb and run ruby spell_suggest.rb "appls and ornages")

The main Google search results page is queried and parsed instead of working with the Google toolbar. The added benefit of this solution is that the suggestions seem to account for context better than the Google toolbar’s. Google’s toolbar suggestions seem to inspect and process each word independently, whereas main search page accounts for the entire phrase. A word which alone might be considered incorrectly spelled may make perfect sense in context (for example, my last name Daly might come back with suggestion Daily when the query is Jason Daly, however the Ruby solution leveraging Google’s main search page returns no suggestion).

6 notes Tags: ruby nokogiri open-uri google code php spelling spellcheck

May 19, 2011

Using intern to Reference Routes Dynamically

In config/routes.rb if routes such as the following exist:

match "users/customers" => "users#customers", :as => :users_customers, :via => :get
match "users/employees" => "users#employees", :as => :users_employees, :via => :get 

match "users/:id/toggle-status" => "users#toggle_status", :as => :users_toggle_status, :via => :put

the views associated with the :users_customers and :users_employees actions above (in this case each displaying a list-view of users of the specified type) likely both have a link referencing the :users_toggle_status route.

Within the toggle_status action the route to redirect back to can be determined by looking at the type of user who’s status is being toggled. A sample version of this action is below.

def toggle_status
    user = User.find(params[:id])
    user.toggle_status!

    if user.type == 'employee'
      redirect_to :users_employees and return
    end

    redirect_to :users_customers and return
end

The condition above and multiple redirect_to’s is cumbersome and unnecessary. Instead, we can use Ruby’s intern method for strings.

def toggle_status
    user = User.find(params[:id])
    user.toggle_status!

    redirect_to ('users_%s' % user.type).intern and return
end

11 notes Tags: ruby rails ruby on rails RoR intern string tips code

April 26, 2011

Reverse Pagination Count with Kaminari

When displaying a threaded conversation with messages paginated by Kaminari, I had the unique requirement to display messages in DESC order by their creation date. This meant that if there were 12 messages with 5 per page, the breakdown would be

  • Page 1: messages 12 - 8
  • Page 2: messages 7 - 3
  • Page 3: messages 2 - 1

To actually display the message number next to each message, it had to be calculated using the methods added to a paginated model by Kaminari (see the :message_number value below)

<div class="comments">
  <% @messages.each_with_index do |message, index| %>
    <%= render :partial => 'message', :locals => {:message => message, :message_number => @messages.total_count - (@messages.current_page - 1) * @messages.limit_value - index} %>
  <% end %>
</div>

For now this is the only place the above calculation is relevant. Should I need it in multiple places I would move the above calculation to a helper.

3 notes Tags: rails ruby on rails RoR code tips ruby kaminari

April 25, 2011

Parsing and Pluralizing Model Names in Rails

Recently working with Kaminari, I wanted to have text display next to the pagination links like

283 Items - Page 3/15

I wanted the Items text to be dynamic according to the model the paginated collection was representing. Looking over my model names I found converting each to a human readable version would work great in all cases I needed a paginator. Simply using pluralize (I use singular form for my models) will not work in all cases.

  • User would become Users - great!
  • Page would become Pages - great!
  • ConversationMessage would become ConversationMessages - yuck!

For the ConversationMessage model, I really only wanted to display the items in the collection to the user as Messages, not ConversationMessages or even Conversation Messages. I found this was common for my other models on the many side of a one-to-many relationship too (ie. for my UserComplaint model, I wanted the pagination text to say simply Complaints).

The following works beautifully for all models I currently have in place

@collection.first.class.name.underscore.humanize.pluralize.titleize[/\s?([^\s]+)$/, 1]

With models having their default names (in the plural form), the .pluralize call can be removed.

4 notes Tags: ruby ruby on rails rails RoR code tips Kaminari pagination paginator