Sunday, March 29, 2015

Format your Time the easy way with Rails locales

If you haven't ever used locales in Rails before, you're in for a treat. This is a super powerful feature bundled with Rails in the rails-i18n gem. The Ruby on Rails guide for it packs a lot of info to cover all types of scenarios, but I'm going to talk here about a couple very simple tweaks you can make to easily format your Date, Time, and Datetime objects consistently across your entire site.
First, this post is going to assume your app is written in English, and the instructions that follow are written specifically for that scenario, but locales work for any language, so you just need to place your code in the proper YAML file.
Let's say you want all of your Datetime or Time objects in your app formatted in the Month Date, Year - HH:MM Meridian format (i.e. June 25, 2009 - 10:24 PM). The old fashioned way is to have to use .strftime('%B %d, %Y - %H:%M %p'). Don't get me wrong, I love me some strftime, but I don't want to be having to type or copypasta that stuff every time. Here's what we're going to do instead:

  # config/locales/en.yml
  en:
    time:
      formats:
        full: "%B %d, %Y - %H:%M %p"
Then, in your views wherever you want your Time object (for example, object.created_at)to be formatted this way, instead of this:

  object.created_at.strftime('%B %d, %Y - %H:%M %p')
Do this:

  l object.created_at, format: :full
The 'l' prefacing your object is a locale method, and the Time object gets passed in as an argument, with the formatting option that you want to use. Now, if you have a couple different formats you use throughout your app, say :full, :date_only, and :time_with_zone, this makes it so much easier to use.

  # config/locales/en.yml
  en:
    time:
      formats:
        full: "%B %d, %Y - %H:%M %p"
        date_only: "%B %d, %Y"
        time_with_zone: "%-m/%-d/%y %-I:%M %p %Z"
Instead of trying to remember the alphabet soup of strftime, it is way cooler to just call your locale method with the human-readable format you want.

Saturday, March 28, 2015

Capybara tests with Rails `disable_with` on forms

Here's something that threw me for a loop for a little bit.

When using the disable_with option on form submit buttons, under the hood this is running some minimal JavaScript code, so Capybara tests have to be run as js: true, otherwise they will fail.

Wednesday, March 25, 2015

Rails' flash vs flash.now

I've seen these two things on several projects and never knew the difference. Until now!

 Boiled down to the very basics, use flash.now before a render call, and flash before a redirect_to.

  flash persists across an action, so if you use flash for a render, the flash message will show up on the rendered page AND the next page after clicking a link. Conversely, if you run flash.now on a redirect_to, you won't even see the message because the flash.now doesn't persist across requests. 

Thanks to these resources:
http://trace.adityalesmana.com/2010/10/difference-between-flash-and-flash-now-in-ruby/
http://ionrails.com/2009/09/19/flashnotice-vs-flash-nownotice/

Tuesday, March 24, 2015

Attach files and configure `delivery_method` in Rails ActionMailer

Today is a twofer!

Attachments in ActionMailer for Rails

First, I never tried sending attachments in a Rails mailer before, but it is incredibly easy. Just throw this line into your mailer method:

  attachments["screenshot.png"] = File.read("#{Rails.root}/public/images/screenshot.png")

And of course, replace the file name in the attachments hash with what you want the attachment name to be, and then replace the path in the File.read with your attachment's path.

Specify :delivery_method for individual mailer methods

This part can come in handy if you have a specific mailer method that you want to actually send in the testing environment, instead of just outputting to a file or to the log. The specific use case I have for this is when I have finicky intermittent test failures when running our test suite on my CI service (something I've talked about elsewhere). This method grabs the screenshot that was taken when the error occurred, and it emails it to the email passed in:

  def error_email(email, public_image=nil)
    attachments["screenshot.png"] = File.read("#{Rails.root}/public/images/screenshot.png")
    mail(to: email, subject: 'Capybara error', delivery_method: :smtp)    
  end

Since this is happening on my CI server and I can't look at any tmp files it creates, I need the email to actually send via SMTP, but I still want every other mailer method to deliver via the :file method because I don't want to get a boatload of emails every time the test suite runs.

Pretty nifty! Thanks to jankubr on StackOverflow for this one.

Monday, March 23, 2015

Caveat to omitting `self` in Model method calls

I wanted to add a caveat to something I stated in a previous post about leaving self out when calling methods from inside other methods in a model. This WILL give you problems if you are trying to assign values to a Model attribute.

For example, doing this:

  def change_object_name
    name = "Steve"
  end

All this is doing is setting a local variable called name equal to "Steve." If your Model has an attribute called name and you want to set it, you have to use self.name:

  def change_object_name
    self.name = "Steve"
    # Oh, and don't forget to save!
    save
  end

Sunday, March 22, 2015

Undo your last git commit

If you check my Google search history, one of the most frequent searches you'd probably see is "undo git commit." This is primarily from pulling the latest master or staging branch, making changes and committing accidentally before creating a new branch. So, I create the new branch with my new commit on it, but then I want to switch back to master or staging and nuke that last commit. Here's the very simple one-liner I use, taken directly from this extremely helpful StackOverflow answer:

  git reset --hard HEAD~1

Make SURE you have created that new branch so that you have your commit somewhere else, because this git command will completely erase your commit from the branch in which you are currently working.

Saturday, March 21, 2015

Get easy snippets in Rails views with `truncate`

Silly me, when I had a Note model that I wanted to show just a snippet of the note content with a link to view the full note, I wrote three separate methods that worked in tandem to do this. Then, I found out about truncate:

  truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
  # => "Once upon a..."

  truncate("Once upon a time in a world far far away", length: 17)
  # => "Once upon a ti..."

The separator option will only truncate the text at that char, so in this case, it won't break up words, it will get to 17 characters, then find the previous space (or whatever character is specified as the separator) and truncate there. As you can see, without the separator option, it counts up to 14 and adds an ellipsis to make the total character count 17.

There are several other options to extend this that you can check out in the docs. NOTE: This can be used by default in Rails views because it's part of ActionView::Helpers::TextHelper, but must be specifically included if you want to use it elsewhere in the app.

So save yourself some time and trouble, don't be like me and start making up snippet methods when there's a perfectly good one already built!

Friday, March 20, 2015

Bash yourself multiple times

Here's a quick Bash trick. Run command line commands multiple times:

  for i in `seq #{run_how_many_times}`; do #{command_to_run}; done

Example:

  for i in `seq 10`; do rspec spec; done

This is super helpful in debugging tests with intermittent failures.

Thursday, March 19, 2015

Using `self` in Model methods

A friend recently asked me about this, and although I already knew it, I couldn't remember when I learned it, so I'm putting it in here now.

When calling another method from within a Model instance method, you don't strictly need to preface it with self:

  class Person
    def status
      ‘admin’
    end

    def admin?
      self.status == ‘admin’ ? true : false
    end
  end

This works exactly the same:

  class Person
    def status
      ‘admin’
    end

    def admin?
      status == ‘admin’ ? true : false
    end
  end

In fact, I almost never use self anymore in Model methods, except for readability purposes.

UPDATE: I neglected to mention the importance of using self when assigning Model attributes. See my new post for more explanation: http://spaghettirefactory.blogspot.com/2015/03/caveat-to-omitting-self-in-model-method.html

Wednesday, March 18, 2015

Find multiple records with ActiveRecord's Model.find

This is something cool that it took me over a year of coding in Ruby to find out. If I have a comma separated list or an array of IDs, I can find all of the records in one call:

  Model.find(ids)

Caveat: This will give the objects back in an array. However, if you want to run ActiveRecord methods on this, better to use this:

  Class.where(id: ids)

Then, you can chain methods like so:

  Class.where(id: ids).update_all(updated_at: Time.now)

Be careful! update_all does NOT run validations. But it is incredibly useful for updating several records without instantiating all of the objects. This is a straight sequel query, hence no callbacks.

Monday, March 16, 2015

present? vs any? in Rails

First, a few definitions, direct from the Ruby docs  and Rails docs:

present?
An object is present if it’s not blank?

blank?
An object is blank if it’s false, empty, or a whitespace string.

any?
Passes each element of the collection to the given block. The method returns true if the block ever returns a value other than false or nil. If the block is not given, Ruby adds an implicit block of { |obj| obj } that will cause any? to return true if at least one of the collection members is not false or nil. 

present? is a Rails method from ActiveSupport
any? is pure Ruby A few important points:

  nil.any? 
  # => NoMethodError: undefined method `any?' for nil:NilClass

  nil.present? 
  # => false

  [""].any? && [""].present? 
  # => true

  [""].any? &:present? 
  # => false

The last example is good if you need to make sure there are no blank values in an array.

 So, how do all of these measure up, performance-wise? Well, they're all pretty similar, but it seems that any? is moderately faster than present, so if you can get away with it, go for that one. However, as far as I'm concerned, when dealing with such a small performance difference, readability becomes more important, so I would personally go for whichever is more readable that still accomplishes my purposes. With arrays, that tends to be any? most of the time.

Benchmarks:

  array = (0..500000).to_a
  Benchmark.bmbm do |x|
    x.report("any with empty array:")   { 100000.times { [].any? } }  
    x.report("present with empty array:") { 100000.times { [].present? } }  
    x.report("any with stuff:")   { 100000.times { array.any? } }  
    x.report("present with stuff:") { 100000.times { array.present? } }  
  end  

                                  user     system      total        real
  any with empty array:       0.010000   0.000000   0.010000 (  0.010986)
  present with empty array:   0.010000   0.000000   0.010000 (  0.015146)
  any with stuff:             0.010000   0.000000   0.010000 (  0.008500)
  present with stuff:         0.010000   0.000000   0.010000 (  0.012448)

Monday, March 2, 2015

Discrimination in Rspec

I'm fully against discrimination in almost all its forms. I say "almost" because, when it comes to testing with rspec in Ruby, I'm fully in favor of it. Here's why: I can tag tests in rspec and run only those tests.

This has a lot of cool use cases, but I'm using it for running rspec tests in parallel on my CI service. For example:

  describe 'get averages but takes a long time', slow: true do
    it 'gets average foo' do
      ....
    end

    it 'gets average bar' do
      ...
    end
  end

Then, to run only tests tagged as "slow", run rspec with this command:

  rspec --tag slow

Another option is to have rspec automatically NOT run certain examples, such as "slow":

  RSpec.configure do |c|
    c.filter_run_excluding slow: true 
  end

Then, to run all examples:
  
  RSpec.configure do |c|
    c.filter_run_excluding slow: true unless ENV[‘ALL’]
  end

Then run rspec with this command:

  ALL=1 rspec

I can also run all tagged tests without doing any rspec configuration using this command:

  rspec . --tag type:special

Or I can run all tests EXCEPT certain tagged tests using this command:

  rspec . --tag ~type:special

Thanks to Myron Marston on StackOverflow for this nifty trick.