Cory O'Daniel – These are just words Software development, thoughts, and randomness

17Nov/112

Rails 3 Easy Search forms using SimpleForm and ActiveModel

For the sites I'm currently working on I have search pages all over the place for different resources, especially on the backend. In the past I would forego using form builders and just do some HTML to make a form GET the data. I hate HTML.

So I did some poking around to see what I could do to DRY up my time on creating these search forms. My first thought was to create a Search class and use active model, but it sucked because I everytime I had to create a new form I had to come into the search class and add a bunch of attr_accessors for all of the search fields. That sucks.

So I decided to mix it up with OpenStruct and ActiveModel

require 'ostruct'
class Search < OpenStruct
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend  ActiveModel::Naming
 
  def initialize(*args)
    super
  end
 
  def persisted? ; false ; end;
end

Now I use this one model for searching of all of my resources.

I create the form super easily with SimpleForm:

= simple_form_for(@search, url: my_cool_path(), html:{method: :get}) do |f|
  = f.input :text
  = f.select :category,  Product::CATEGORIES
  = f.input :minimum_price
  = f.input :maximum_price
  = f.submit :search

In your controller you are going to get a params[:search] hash with :text, :category, :minimum_price, :maximum_price.

Want to persist that search across page reloads?

class ApplicationController < ActionController::Base
  before_filter :persist_search
  protected
  def persist_search
    @search = Search.new params[:search]
  end
end

Now you'll have a Search object in all of your requests with your user's search params. Use it as you will to return them the resources they are looking for.

*Side note*
I use the above logic for all of my resources, until one of my resources has a requirement like a validation. Then I break it into something like FlightSearch:

require 'ostruct'
# ostruct for that lazy goodness
class FlightSearch < OpenStruct
  # Get activemodel in there for all its cool functionality like model name, pluralization, validation, yada yada yada
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend  ActiveModel::Naming
 
  validates_presence_of :origin_airport, :destination_airport
 
  # you can do cool things here, just make sure you call super so ostruct 
  # takes all those hash params and makes them methods
  def initialize(*args)
    super
  end
 
  # ActiveModel needs to know that this wasn't persisted.
  def persisted? ; false ; end;
end

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

7Oct/110

Using devise’s scoped url helpers in Cucumber; new_registration_path, new_session_path

You can use devise's scoped url helpers from cucumber, but you have to use them by their real Url Helper method names:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module NavigationHelpers
  def path_to(page_name)
    case page_name
    when /the sign up page/
      # dont use the helper's helper
      # new_registration_path(:user)      
 
      # use the underlying url helper
      new_user_registration_path
    when /the sign in page/
      # dont use the helper's helper
      # new_session_path(:user)      
 
      # use the underlying url helper
      new_user_session_path
    else
      #...yada, yada

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

12Apr/110

cucumber.yml was found, but could not be parsed. Please refer to cucumber’s documentation on correct profile usage.

Yeah, I got that message today and wasted about 30 minutes of my life.

I hadn't changed my cuke yaml file since I started work on the app.

I tried:
* upgrading cucumber (FAIL)
* using another yaml file (FAIL)
* upgrading gherkin (FAIL)
* hard reseting the branch I was on (FAIL)

Turned out to just be a bum rerun.txt file. So I deleted it.

I need a refund on that 30 minutes.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

24Mar/110

Backdateable mongoid models – a simple little module I use a lot

I have a lot of models in one of the projects I am currently working on that need to be backdate-able. This is just a simple module I add to any class that needs to support it. It could be easily tweaked for ActiveRecord.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module Backdateable
  module ClassMethods;end;
 
  def self.included(base)
    base.extend(ClassMethods)
    #allows this to be an accessor on the front end, although, 
    # if the date is left blank, it gets set properly in before
    base.send(:field, :backdated, type: Boolean, default: false)
    base.send(:field, :recorded_at, type: Time)
 
    if base.respond_to?(:default_scope)
      base.send(:default_scope, :asc, :recorded_at)
    end
 
    # Changing this to before_create only allows backdating on creation...
    base.send(:before_save, :set_recorded_at)
  end
 
  # defaulting recorded at to created_at lets this be sortable by recorded at
  # we override backdated here in case the date was originally left blank
  def set_recorded_at
    self[:recorded_at] ||= self[:created_at]
    self[:backdated] = (self[:recorded_at] != self[:created_at])
    true
  end
end

Now you can do:

1
2
3
4
5
6
7
8
9
10
11
class Measurement
  include Mongoid::Document
  include Mongoid::Timestamps
  include Backdateable
end
 
Measurement.create
# => created_at & recorded_at are set to the same, backdated is set to false
 
Measurement.create(:recorded_at => Time.now.advance(:days => -7))
# => recorded_at is set to the past, backdated is set to true

Why do I default recorded at to created at? So I can sort by recorded_at.

Done, son.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , No Comments
23Mar/110

Biting myself on the ass with Cucumber

So I spent a few hours today biting my own ass with cucumber.

If you are ever trying to "see" stuff in your page and notice that it has strangely disappeared, like oh say, your flash messages or validation errors. You may want to pay close attention if you are testing the page you are on just before hand.

I found out a sore lesson today, there is a big difference between:

1
2
Then I am on the sign up page
And I should see "Email can't be blank"

And

1
2
Then I should be on the sign up page
And I should see "Email can't be blank"

The difference here? "The I am on" reloads the page. I didn't know that. So I was trying to make sure I was on the right page, then checking for errors, etc. The page would reload, all my errors would be gone, and my feature was failing.

"Then I should be on" tests that the url matches the one you are on.

The more you know.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

22Feb/110

Matching Unicode characters and strings with Regexp in Ruby 1.9.2

So I've been working on a revamp of a project (still secret, ohhhh ahhh) and I needed Unicode support for multiple languages. I whipped up a pretty simple regexp and found that it didn't work for my test case.

So I've got something equivalent to this running inside some rails rspec test. And my test is failing because I'm expecting some russian words.

1
2
3
greeting  = "Добрый день."
greeting.scan( /\w/u )
#=> []

WTF.

I double check:

1
2
3
4
5
6
7
8
greeting  = "Добрый день."
pattern = /\w/u
 
puts greeting.encoding
# => UTF-8
 
puts pattern.encoding
# => UTF-8

Hell, I even have "# encoding: UTF-8" at the top of the file.
Fuuuuuuuuuuuuuuuuuuuuuuu.

After some google and some irc'ing I find this answer, which solves the problem and allows for matches of unicode characters in ruby 1.9.2, but I still can't figure out what the 'p' stands for. I'm guessing "please". The problem is apparently there was an issue with \w matching unicode characters, so \w specifically only works with ASCII.

1
2
3
greeting  = "Добрый день."
greeting.scan( /\p{Word}/u )
#=> ["Добрый", "день"]

Wööt.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

2Nov/100

methods_like helper for finding ruby methods

Here is a little gem I have in my ~/.irbrc I use it a lot when debugging code. I tend to use call #methods a lot on objects to figure out how they quack.

Sometimes I can remember kinda what a method's name is I want to use, but doing something like the following can generate a huge amount of output especially when working with ActiveRecord

1
my_object.methods.sort

So I threw together this little method that adds to ruby's base Object class

1
2
3
4
5
6
7
8
class Object
  def methods_like(pat)
    pattern =  pat.is_a?(String) ? /#{pat}/ : pat
    self.methods.sort.select{|m|
      m =~ pattern
    }
  end
end

Now from IRB, Rails console, or Padrino console you can do things like:

my_collection.methods_like(/^update/) #=> [ array of method names that start with update ]
user.methods_like("name") #=> [ :first_name, :last_name, :etc ]
Array.new.methods_like "sort" #=> ["sort", "sort!", "sort_by"]

Its a handy little snippet when you can't remember an exact method name, and generally for me works faster than googling for it. Toss it in your ~/.irbrc and tell me what you think.

UPDATE
I added some more functionality for getting additional methods (private, protected, singleton):

class Object
  # pattern - string or pattern to match
  # access  - :public, :private, :protected, :singleton, :all
  def methods_like(pattern, access = :public, details = false)    
    master_method_list = case access
    when :public then self.methods
    when :private then self.private_methods
    when :protected then self.protected_methods 
    when :singleton then self.singleton_methods
    when :all
      (
        self.methods            +
        self.private_methods    +
        self.protected_methods  +
        self.singleton_methods
      ).uniq
    end
 
    matched_method_list = master_method_list.sort.select{ |m| m =~ ( pattern.is_a?(String) ? /#{pattern}/ : pattern ) }
 
    if !details
      matched_method_list
    else
      details_list = {}
 
      matched_method_list.each do |current_method|
        details_list[current_method] = method_details(current_method)
      end
 
      details_list
    end
  end
 
  # Returns details about method
  def method_details(current_method)
    current_method = current_method.to_s
    access_level = if self.methods.include?(current_method)
      :public
    elsif self.private_methods.include?(current_method)
      :private
    elsif self.protected_methods.include?(current_method)
      :protected
    elsif self.singleton_methods.include?(current_method)
      :singleton
    end
 
    {
      :owner    => self.method(current_method).owner,
      :arity    => self.method(current_method).arity,
      :receiver => self.method(current_method).receiver,
      :access   => access_level
    }
  end
end

Now you can do:

variable.methods_like pattern, :private #=> Array of private methods with pattern
variable.methods_like pattern, :all, true #=> Hash of methods with additional details

If you pass 'true' for details you will get a hash that looks like:

Array.new.methods_like "sort", :all, true #=>
{
  "sort_by"=>{:owner=>Enumerable, :access=>:public, :receiver=>[], :arity=>0}, 
  "sort!"    =>{:owner=>Array, :access=>:public, :receiver=>[], :arity=>0}, 
  "sort"     =>{:owner=>Array, :access=>:public, :receiver=>[], :arity=>0}
}

Access options available are :public, :private, :singleton, :protected, :all

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

7May/101

Ruby 1.9 Date#strftime adds a space on %b

So, I had a spec failing in my integration suite and I couldn't figure out what the hell it was - EVERYTHING LOOKED LEGIT. I even logged into the site, and visually verified it. Whats the dilly?

I upgraded to ruby 1.9 from ruby 1.8 and apparently, when doing strftime on date, you get a free whitespace character with "%b"

1
2
3
# Ruby 1.8
irb(main):006:0> Date.today.strftime("%b %e, %Y")
=> "May 7, 2010"
1
2
irb(main):007:0> Date.today.strftime("%b %e, %Y")
=> "May  7, 2010" # TWO SPACES

Super dumb - wasted a good 10 minutes of my day because I could see the diff between:

1
2
"May  7, 2010"
"May 7, 2010"

In the middle of a web page full of other content.

Why the freebee space character on the %b? Dunno. I just smashed my stftime statement together and my specs are rolling.

1
Date.today.strftime("%b%e, %Y")

Uh, yeah - this is my bug report.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

17Apr/100

Padrino, Compass, and Sass – Working happily via Ian Serlin

My cohort, Ian Serlin, discovered this. In a project we are working on we could get Compass to play well with PadrinoRb. It seems like the #sass method doesn't care about the options being passed to it, and we kept getting stack traces rendered into our CSS files. The stack trace was ruby looking for - and failing to find compass/reset.css.

This is the code that DOESN'T work (padrino 0.9.10, compass 0.8.17, sinatra 1.0).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  configure do
    register SassInitializer #The Rack Sass reloader...
 
    Compass.configuration do |config|
      config.project_path = File.dirname(__FILE__)
      config.sass_dir = "stylesheets"
      config.project_type = :stand_alone
      config.http_path = "/"
      config.css_dir = "stylesheets"
      config.images_dir = "images"
      config.output_style = :compressed
    end
  end   
  get '/stylesheets/:file.css' do
    content_type 'text/css', :charset => 'utf-8'
    # This is the doc on how to give Sass some more load paths to find the compass  files. 
    #   it doesnt work :P and for that matter, neither does:
    # sass :file, Compass.sass_engine_options
    # or the set :sass, whatever_hash_here
    sass :file, :sass => Compass.sass_engine_options
  end

Our solution was to force Sass options and Compass options to merge...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  configure do
    register SassInitializer
 
    Compass.configuration do |config|
      config.project_path = File.dirname(__FILE__)
      config.sass_dir = "stylesheets"
      config.project_type = :stand_alone
      config.http_path = "/"
      config.css_dir = "stylesheets"
      config.images_dir = "images"
      config.output_style = :compressed
    end
 
    Sass::Plugin.options.merge!(Compass.sass_engine_options)
  end
 
  get '/stylesheets/:file.css' do
    content_type 'text/css', :charset => 'utf-8'
    sass :file
  end

Yay, magic spells - it works. You can check out more ian magic spells at ianserlin.com.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

5Mar/106

Attaching local or remote files to Paperclip and Milton Models in Rails (Mocking content_type and original_filename in a Tempfile)

I was working on a project today where I needed to import some data from MySpace accounts (yeah, MySpace), which included importing the users profile image. In the controller that did the importing I was using OpenURI to retrieve the image and then turn it into a Tempfile to be attached the the model, like so:

1
2
3
4
5
6
7
def import
#...snip
  tempfile = Tempfile.new( my_filename)
  tempfile.write open( image_url ).read
  @imported_user.images.create(:file => tempfile)
#...snip
end

This doesn't work. It blows up missing one of two methods:

  1. #original_filename
  2. #content_type

If you inspect a normal file upload in Rails which has these methods, you'll find that is just a regular old Tempfile. But it has the methods! If you create a Tempfile manually, it won't have the methods. That's because Rails magics them on. I am not sure why they don't create a subclass like Rails::Tempfile that contains these methods and just use that. I guess its because OOP is retarded (sarcasm).

So, I wrote a little subclass that will take a file path, and quack like the magic'd rails Tempfiles you get from an upload so you can attach local files to models or even remote files.

This works pretty straightforward and I'm currently using it in production. It won't work on windows systems because the dependency on the 'file' binary. Also, some linux systems are missing this library by default, so make sure you yum|dpkg|apt|port or whatever to get it installed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require 'open-uri'
require 'digest/sha1'
 
class RemoteFile < ::Tempfile
 
  def initialize(path, tmpdir = Dir::tmpdir)
    @original_filename  = File.basename(path)
    @remote_path        = path
 
    super Digest::SHA1.hexdigest(path), tmpdir
    fetch
  end
 
  def fetch
    string_io = OpenURI.send(:open, @remote_path)
    self.write string_io.read
    self.rewind
    self
  end
 
  def original_filename
    @original_filename
  end
 
  def content_type
    mime = `file --mime -br #{self.path}`.strip
    mime = mime.gsub(/^.*: */,"")
    mime = mime.gsub(/;.*$/,"")
    mime = mime.gsub(/,.*$/,"")
    mime
  end
end

Usage is pretty simple:

1
2
3
remote_file = RemoteFile.new("http://www.google.com/intl/en_ALL/images/logo.gif")
remote_file.original_filename #=> logo.gif
remote_file.content_type #= image/gif

Using it in your controller:

1
2
3
4
5
def import
  #...snip
  @imported_user.images.create(:file => RemoteFile.new( url_to_image ))
  #...snip
end

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon