1. Rails Finite State Machine: AASM != perfect, but > Workflow

    I recently re-wrote an old model that desperately needed a formalized state machine definition (instead of the fragile logic I originally strung together).  After reviewing both AASM (formerly acts_as_state_machine) and Workflow, I went with the former for a couple of reasons.

    1. AASM seemed to be a bit more mature
    2. It made more sense to describe an event (i.e. state machine input) once, with all the transitions it could cause and its to/from states, rather than defining it a separate time for each state-transition pair it could affect.

    For example, one might define a traffic light in AASM as follows:

    aasm_state :red, :yellow, :green
    aasm_event :timer_done do
    	transitions :from => :red, :to => :green
    	transitions :from => :green, :to => :yellow
    	transitions :from => :yellow, :to => :red
    end

    Whereas Workflow would have one define it:

    workflow do
    	state :red do
    		event :timer_done, :transitions_to => :green
    	end
    	state :green do
    		event :timer_done, :transitions_to => :yellow
    	end
    	state :yellow do
    		event :timer_done, :transitions_to => :red
    	end
    end

    I suppose it depends on the situation whether it’s clearer to define states - and everything that can cause one to leave that state - or to define events - and all the states on which they operate.

    But beware: AASM is not a perfect finite state machine.  You cannot auto-transition out of the initial state via the :after_enter method.  The initializer code will set your model’s state variable to whatever is defined by aasm_initial_state after all callbacks are run, even if the state machine transitions to a new state due to those initial states’ callbacks.

    For example, the following code would result in a :red initial state, not a :green initial state as would be expected:

    aasm_initial_state :red
    aasm_state :red, :after_enter => Proc.new { |x| x.go_green! }
    aasm_event :go_green do
    	transitions :from => :red, :to => :green
    end

     

    tags:  rails  bug  gem  state machine 

    Comments
  2. Don’t mark my app’s email as SPAM

    Judging by the number of email outsourcing companies, there is clearly a large problem to be solved.  I’ve had sporadic issues with email being marked as SPAM with HomeField, and then regular issues on my latest project, FoundersCard.  I’ve finally got them fairly well sorted, as far as I can tell…

    1. The DNS records on your name sever must be setup correctly (and verified)
      1. The A record of the domain you’re actually sending mail from (i.e. teamhomefield.com) must point to the IP of the actual server sending the email (in this case 8.12.42.205)
      2. You should also configure the SPF record which is being used increasingly by recipient mail servers to further determine if the sending servers IP address is authorized to send email for the given domain.  My DNS has a TXT record with the value “v=spf1 a mx ptr ~all”.  
    2. rDNS (reverse DNS) must be setup
      • On the server sending the mail, the PTR record must correctly specify the hostname pointing to this sever (teamhomefield.com)
    3. Your Rails setup…
      • You can set from to be any username at the domain configured above, but I’ve found that GMail will mark SPAM unless the following format is used
        • from  ”anything@teamhomefield.com (Anything Else)”
        • Not sure why “Anything Else <anything@teahomefield.com>” doesn’t work..
      • The reply-to is great for sending on behalf of a user
        • reply_to “Dan Spinosa <spinosa@gmail.com>”
      • I am using the following ActionMailer configuration
        • config.action_mailer.sendmail_settings = {
            :location       => '/usr/sbin/sendmail',
            :arguments      => '-i -t'
          }
    4. Your emails content…
      • If you’re sending HTML email, be sure to send it as a multipart email including a text part that closely matches the HTML (which, BTW, should include <html> and <body> tags)
        • In Rails, if your mailer action is named “notify_email”, Rails will automatically create a multipart email for you if you have multiple views: notify_email.text.html.erb and notify_email.text.plain.erb
      • It’s free to check your spam score & deliverability at contactology
    5. Your mail servers IP…
      • Make sure it is not listed by Spamhaus, and remove it if it is.  It is common to see your IP in their PBL, which can easily be remedied.

    HTH, and hope I don’t have to deal with this again.

     

    tags:  SPAM  rails  GMAil  fail 

    Comments
  3. Rails DB Backup to S3

    If you don’t believe backing up your data is important, come back when (not if) you lose some important information.  Sensible (Rails) developers, read on…

    I’ve seen (and used) decent backup solutions involving mysqldump, sftp and an additional server.  They’re okay, but with a few plugins/gems your DB backup can be elegant (and Amazon secure).  I happen to like db2s3.  Configure the gem like the README says.  Then add a couple of whenever tasks:

    every 1.hour { rake “db2s3:backup:full” }
    every 1.day { rake “db2s3:backup:clean” }

    Holy shit done.  But you will have to pay the S3 bill.  So pick up a penny from the ground every day.  You’ll be $.25 ahead of the game at the end of the month.

    Gotchas?  Of course.  db2s3 relies on aws-s3, so you must have that gem installed (this is not explained in the README).  NBD, but if you happen to be using right_aws with paperclip, like me, you may run into issues due to the gem load order.  So, keep your house in order:

    config.gem “aws-s3”, :lib => “aws/s3”, :version => ‘>= 0.6.2’
    config.gem “right_aws”

    Thoughts?

     

    tags:  rails  S3  backup  ruby 

    Comments
  4. Confusion Over Configuration

    Many Ruby on Rails decisions are guided by the motto “convention over configuration.”  This makes Rails opinionated software and allows one to get up and running quickly and easily, so long as you follow the crowd.  I love this.  It makes you confident you can try something new, get it up and running fairly easily, then start adding functionality, breaking and tweaking iteratively.  They so to err is human.  It’s also Rails…

    I’m a very organized person; it helps me get things done.  So when I recently started building some Rails billing functionality, I decided I would namespace it.  Shouldn’t be too bad…

    script/generate model billing/Account ...

    This created the model Billing::Account in the file models/billing/account.rb.  Nice.  Turned out to be less-than-nice when I tried to start working with it.  I’ll spare you the details of how much I hated my life that day and get straight to the caveats:

    1. The migration generated created a table called “billing_accounts” which seemed correct (and great!) to me.  But ActiveRecord doesn’t care about your namespace, so you have to manually add “table_name :billing_account” to the Billing::Account model.
    2. For testing, a new directory was created: test/fixtures/billing, but it was empty.  The file billing_account.yml was added to the base fixtures directory.  Don’t do what I did and move that file into the billing/ directory: those fixtures don’t get loaded.  So leave it, or move it and update Rails’ load path to look through fixtures/ recursively.
    3. Foxy fixtures do not work with namespaced models.  Not at all.  If there’s a way to get them to play nice, I couldn’t figure it out before deciding to hand out a couple of id’s manually.  Fuck it, that’s why.

    It puzzles me that Rails’ generator seems to handle namespaced models so gracefully, but actually creates these incompatible time-sucking gremlins.  Did I do something completely wrong here?

     

    tags:  rails  fail 

    Comments
  5. Flash Now!

    In development of a new, greenfield application I had flash messages popping up twice: once for the current page, and then again on the next page.  It turns out I was using flash[]= and then rendering within the same action.  As flash[]= is designed to carry over to the next action (i.e. after you redirect, a new action is performed) it was showing up again.  The solution: flash.now[]=.  This hash is cleared after the current action.  The following code illustrates when to use each:

    def create_application
      #do work
      if it_worked?
        flash[:notice] = "Great success!"
        redirect_to @cash
      else
        flash.now[:error] = "Let's try that again"
        render :action => "new"
      end
    end
    

    The flash[:notice] must be carried over to the action that handles the @cash path (following to the redirect), whereas the flash.now[:error] will be shown for the current action (even though it renders a different view) and should not be carried over to the next action.

    It seems somewhat strange to me that I’m just discovering this now.  I suppose that as I learn more about Rails and web applications in general, I adjust my techniques and start experimenting with new ones (that aren’t always better).  The fact that flash[]= carries over to the next action and flash.now[]= doesn’t has only come into play now that I’m using flash as more of a central messaging system than just the standard notices from scaffolds.

     

    tags:  ruby  rails  fail 

    Comments
  6. db:fresh - Easily rebuild your DB from scratch

    I suppose the idea behind Rails’ migration is that once you create them, you don’t change them.  This is usually the right concept, and from this decision grows many of the rake db:* tasks.  Such tasks — migrate, reset, schema:load, schema:dump, and setup — do not re-run migrations as they assume schema.rb is up to date (rightfully so).  But oftentimes (especially on greenfield development) I will tweak my migrations and want to start from scratch.  So here’s a quick hitter I now have my application templates add to every project I start:

    module DBTasks
      namespace :db do
        
        desc "drops, creates, migrates and seeds the DB"
        task :fresh => :environment do
          puts "Dropping DB..."
          Rake::Task["db:drop"].invoke; puts "done."
          puts "Creating DB..."
          Rake::Task["db:create"].invoke; puts "done."
          puts "Migrating DB..."
          Rake::Task["db:migrate"].invoke; puts "done."
          puts "Seeding DB..."
          Rake::Task["db:seed"].invoke; puts "done."
          puts "Your DB is so fresh and so clean clean!"
        end
        
      end
    end
    

     

    tags:  rails  ruby  db 

    Comments
  7. Whenever: A Hidden Gem

    After learning cron syntax, figuring out what the PATH is during cron execution and how to set it in my crontab, and documenting my crontab within my application, I discovered Whenever.  Javan Makhmali’s Whenever gem allows you to write self-documenting ruby in your application to define your cron jobs, then integrates beautifully with Capistrano to update  your crontab on deploy.  It also saves me from having to lookup cron syntax every few months when I need to edit something (and have, of course, forgotten the syntax).

    The documentation for Whenever is very good, and there’s even a Railscast that provides a step by step.  But there is one setting not mentioned anywhere, which I only found digging through the code:

    set :set_path_automatically, false

    Nominally, Whenever writes a PATH=... line before it lists your cron jobs.  This is fine most of the time, but on some systems (like Joyent’s SunOS 5.11) the extended Vixie Cron syntax (the standard used on most *nix systems) is not supported.  As such, you may see an error like

    crontab: error on previous line; unexpected character found in line.

    if PATH is set in your crontab.  So, disable it in your schedule.rb and get back to real work.

    p.s. Will Tumblr ever create a post type for code?

     

    tags:  ruby  error  gem  rails 

    Comments
  8. Rails Session Domain

    Rails’ environment.rb (like most of the generated files) has a lot of comments that help you learn rails and build your app.  Until yesterday I never used the :domain key of the action_controller.session, and I’m somewhat surprised Rails’ make no mention of it in environment.rb.  But I have been using Rails for almost 2 years now, so I suppose it’s not in there because you can get by without it for quite some time =)

    After upgrading our SSL certificate to use a wildcard, I directed secure pages to the secure subdomain (i.e. https://secure.SportKong.com) to really drive home the point that it’s secure.  I then directed non-secure pages to the regular domain (http://SportKong.com) but the users’ session was lost.  I needed to set config.action_controller.session[:domain] = ".sportkong.com" so that sessions would span across multiple domains.

     

    tags:  ruby  rails 

    Comments
  9. Rails InvalidAuthenticityToken

    I am a big fan of the Rails plugin exception_notification.  It’s dead simple to integrate with your app and it alerts you to any problems as they happen.  You get an email with all the information you need to resolve the issue.  So, after reading the email you 1) fix the bug 2) cap deploy 3) email the affected user and 4) profit (i.e. make your user happy)!

    For a few weeks now I’ve been getting emails with the subject ActionController::InvalidAuthenticityToken.  I wasn’t sure why, and couldn’t track it down since I couldn’t reproduce it.  When using restful_authentication you get two logout methods: logout_keeping_session! and logout_killing_session!. As it turns out, I was killing the session in a couple of controllers.  If the user then tries to log in, their authenticity token will no longer be valid, which produces the aforementioned InvalidAuthenticityToken error.  (The plugin expressly warns against using logout_killing_session!, by the way).

    Rails is powerful.  You can express a lot with little code, and the framework supports you from all over.  This results in a learning curve that isn’t steep, but is longer than most.  I learn something new about Rails (and Ruby) weekly, usually right after I break it.  That’s how I (like to) learn.

     

    tags:  ruby  rails  error  bug 

    Comments
  10. blog comments powered by Disqus