Wednesday, January 28, 2015

How to make a ruby executable file

Thanks to Commander Coriander for this next bit. I have "borrowed" heavily from the guide there.

How to make a ruby executable file, a fairly major step in gem creation

First, create a Ruby script that will display something. Doesn't matter what, we just need to be able to test that the final executable is working. Let's call our program "awesome_sauce.rb"

Next, instead of having to type ruby awesome_sauce.rb to run our script, let's get it so we can just type awesome_sauce.rb and it infers that we want to use Ruby to run it. To do that, we just need to add the following to the very top of our script:

  #!/usr/bin/env ruby

This is called a "Bash directive" and it tells Bash what program to run our file with by asking for the current configured version of Ruby as specified by the env command. Check out man env for more info on how env works, but essentially, it will use whatever version of Ruby that's configured in your shell session. To find out which Ruby version that is, just run ruby -v

Now we need to make our script executable, so we have to give the file an execute permission. Wikipedia is great for reading all about file permissions for the uninitiated (http://en.wikipedia.org/wiki/File_permissions#Traditional_Unix_permissions). Doing that is just a simple Bash command. Assuming you're already in the same directory as your Ruby script, run this command to add execute permissions:

  chmod 755 awesome_sauce.rb

We're going to add execute permissions which will appear as an x in that line. If you check the file permissions by running ls -l awesome_sauce.rb, the output should be something like this:

  -rwxr-xr-x  1 username  staff     28 Jan  14:02 awesome_sauce.rb

The 'x' as the fourth character tells us that the file can be run directly without calling Ruby first. The following command should get our script to run:

  ./awesome_sauce.rb

It's now an executable! All that's left is cleanup, i.e. removing the prefix ./ and the suffix .rb. The suffix part is easy - just rename your file to awesome_sauce. I still do file renaming with the GUI, but here's how you can do it command-line-ninja style:

  mv awesome_sauce.rb awesome_sauce

Finally, to remove that prefix ./. The reason that is there is because every time we call a Bash program, Bash searches through a predefined list of folders looking for those programs, called the path. Your path is stored as an environment variable on your computer, and can be seen by running:

  echo $PATH

The output should be a long string of various system-critical folders, separated by colons. The one we're looking for is /usr/local/bin/, which is where any user additions should go. If that folder doesn't exist, create it:

  mkdir -p /usr/local/bin/

Now, rather than actually moving our program, let's instead create a symlink (also known as softlink or alias) within the /usr/local/bin/ folder. (To read more about symlinks, short for Symbolic link, Wikipedia is your friend.) To create a symlink, make sure you're still in the same directory as your awesome_sauce and use the ln command:

  ln -s $PWD/awesome_sauce /usr/local/bin/

The $PWD variable will expand to an absolute path to our delicious awesome_sauce.

And you're done! You've now got a fully executable awesome_sauce Ruby script. Pretty slick, friendo.

Tuesday, January 27, 2015

Speed up your ActiveRecord queries with Model.select

Say you want to display a list of all your users' names and link to their show pages. Instead of running User.all, instantiating every field of every user, there's a better way. Since you only need the name and id or slug (for the link), better to run:

@users = User.select(:name, :email)

That will instantiate user objects with just those attributes, saving memory and database query time. 

Also, brand new for Rails 4.2, you can presumably print out the fields selected by called @users.first.accessed_fields. This is primarily helpful in development mode so you know which fields you have access to. However, I couldn't get this to work locally, might need to be using master branch of Rails. Read more here: https://github.com/rails/rails/commit/be9b680

Monday, January 26, 2015

Simple bundler trick to see what gems and versions are installed

So you want to see what gems and which versions are installed for your app. Sure, you could simply look at your Gemfile.lock. Or, you can run this handy command: bundle show

Friday, January 16, 2015

Pitfalls when using Rails `render_to_string`

I spent a couple hours debugging this one and thought I should write it down:

#render_to_string needs the full file extension for templates and partials passed in, and it also needs locals defined even if the local is an exposed variable or an instance variable.

For example, the approach detailed in the previous post:

    object_details = JSON.parse render_to_string(partial: 'path/to/object/detail.json.jbuilder')

It needs locals defined in order to work properly:

    object_details = JSON.parse render_to_string(partial: 'path/to/object/detail.json.jbuilder', locals: { object: object })  

Also need to ensure if there are any nested partials within the partial being rendered to string, those partials need the file extension as well.

Thursday, January 15, 2015

'Publishing' an object and associations using serialized fields

This is one I'm pretty proud of. It's certainly not perfect, and we'll see how future proof it is, but I think with the problem I was trying to solve, it came out as a fairly elegant solution.

On our project, we had, ironically, a Project model that had all kinds of has_many associations on it. We needed a user to be able to "publish" their project and it would essentially take a snapshot of the project object and all association data at that time, and be able to server it in JSON for API calls.

I made a PublishedVersions polymorphic model with a publishable_id and publishable_type, and a serialized `details` field so it can just hold a JSON hash in the right format.

class PublishedVersion < ActiveRecord::Base
  serialize :details
  belongs_to :publishable, polymorphic: true
end

Then, when the user publishes an project, in whatever action that form goes to (:publish or :update, probably), we run

  object_details = JSON.parse render_to_string(partial: 'api/projects/detail.json.jbuilder')

to render the proper project detail response as JSON.

After that, we persist that response right into the details field of the PublishedVersion object.

PublishedVersion.create(publishable_type: ‘Project’, publishable_id: project.id, details: published_details)

Our Project class gets a has_many :published_versions, as: :publishable relation. This way, there is a history of published versions which user can view as snapshots.

If they really screw up the work-in-progress version of Project, they could revert to a previous version potentially, although this kind of functionality would be better handled by using something like paper_trail.

(The reverting process would have to be built as a separate method or class even, and it would have to find a published version by timestamp or id, and create or update all of the instances and associations. Nonetheless, the data would be in the database, so it's completely doable.)

For API calls to get the published version, since it’s already in JSON format, we don’t even need to render a jbuilder, we can simply run:

render json: object.published_versions.last.details

I made a helper method for this, #most_recent_published_version, that calls the same code.

Wednesday, January 14, 2015

Very VERY basic Rails caching

If you're doing any sort of complex caching in Rails, you're probably not going to be using these below methods too much, but they're cool to know anyway.

Rails.cache.write(‘#{cache_key}’, object)
Rails.cache.read(‘#{cache_key}’) # => object
Rails.cache.exist?(‘#{cache_key}’) # => true

NOTE: This will NOT cache associations of that object. To do that, you would need to run:

object.associations.each do |assoc|
    Rails.cache.write(‘#{assoc_cache_key}’, assoc)
    Rails.cache.read(‘#{assoc_cache_key}’) # => assoc
    Rails.cache.exist?(‘#{assoc_cache_key}’) # => true
end

You can set these caches to expire with certain options. More at http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html

Also, make sure you're aware that if you write to the same cache key with different data, it will simply overwrite that cache. It's good practice to use the object ID as part of the cache key for doing any sort of dynamic caching, unless you're just caching static content.

Monday, January 12, 2015

A Primer on Time Zone parsing in Rails with ActiveSupport

To get time zone abbreviation (ie ‘PST’ for Pacific Time), you can call #zone method on any Time object. However, ActiveSupport::TimeZone parsing does not work with that zone format, so if you need to parse a time object by zone, better to get the full zone name. Here’s a method that does that. 

This is a helper method for views, call by itself, not on an object:

  def time_zone_name(any_time_object)
    utc_offset = time.to_s[-5..-1]
    # UTC offset example: Eastern Time == '-0500'
    # We need it in integer format -5
    ActiveSupport::TimeZone[utc_offset.gsub!('0','').to_i].name
  end

# => ‘Bogota’ (which is the same time zone as Eastern Standard Time (US and Canada)

Now you can use this time zone string to parse Time objects with ActiveSupport::TimeZone

ActiveSupport::TimeZone[“Bogota”].parse(Time.now.to_s)
# => outputs the current time with -0500 UTC offset, aka Eastern Time

Caveat: it will output that time with the abbreviation for Bogota time zone, COT. Example: Mon, 12 Jan 2015 15:50:57 COT -05:00 The time and UTC offset will be the same as for EST, and should behave similarly when parsing, but it is still something to be aware of.

Sunday, January 11, 2015

One simple way to get data from a Rails view into a JavaScript file

I had a tricky problem where there was some data from the server that was being sent to the view in query params, and I needed it in my JS file to selectively show/hide elements. Here's what I ended up doing.

In my view (HAML syntax, for the uninitiated), I am essentially creating an empty div, giving it a unique ID, and filling in data attributes with the data I need. (NOTE: start_date, start_time, and time_zone are variables defined elsewhere):

  #video-or-in-person-fields{ 'data-start-date' => start_date, 'data-start-time' => start_time, 'data-time-zone' => time_zone }

Then, in my JS file:

  start_date = $("#video-or-in-person-fields").attr('data-start-date')
  start_time = $("#video-or-in-person-fields").attr('data-start-time')
  time_zone = $("#video-or-in-person-fields").attr('data-time-zone')

Whammy.

Saturday, January 10, 2015

Handling datetime in JavaScript (or more accurately, CoffeeScript)

Working with Date, Time, and DateTime seems to be pretty awful in any language, but compared to Ruby and ActiveSupport, doing it in JS or Coffee is awful. You can get libraries that make it a little easier to work with (I believe Moment.js is one such that can be very helpful), but for simple formatting things, it's easier and more lightweight to just write your own functions.

 This gives today’s date in YYYY-MM-DD format:

  date_today = ->
    today = new Date()
    dd = today.getDate()
    mm = today.getMonth() + 1
    yyyy = today.getFullYear()
    if dd < 10
      dd = '0' + dd
    if mm < 10
      mm = '0' + mm
    today = yyyy+'-'+mm+'-'+dd
    return today

This gives the current time in HH:MM:SS -0800 format, where -0800 is the timezone offset from GMT, and the HH are displayed in 24 hour time:

  time_now = ->
    right_now = new Date()
    hh = right_now.getHours()
    mm = right_now.getMinutes()
    ss = right_now.getSeconds()
    time_zone = right_now.toString().split(':')[2].substr(6, 5)
    if time_zone == ""
      time_zone = "UTC"
    right_now = hh+':'+mm+':'+ss+' '+time_zone
    return right_now

Friday, January 9, 2015

Very VERY basic Vagrant commands for a n00b

This is nothing that can't easily be found in the docs, but it was new to me:

  vagrant up

This uses a Vagrantfile to start up a virtual machine (such as starting up an Ubuntu environment using VirtualBox)

  vagrant ssh

This will SSH into the virtual machine, but first you need to ensure that SSH keys are configured properly. These can be passed in by setting this in the Vagrantfile:

  config.ssh.forward_agent = true

Thursday, January 8, 2015

Joining two join tables in Rails

Joining data from two join tables in Rails is gross, but sometimes necessary.

Let’s say I have a join table items_categories and a join table items_groups. I want to get all items that are in the same category and the same group, by category and by group ids. Here's how I did it:

  Item.joins(:items_groups).joins(:items_categories). 
       where('items_categories.category_id = ?', category_id).
       where('items_groups.group_id = ?', group_id)


UPDATE: Since posting this, we have changed how we handle this and made a model to holds all three IDs so there is a three-way relation. This seems to be a better approach than the above, but it was fun making all those joins anyway.

Wednesday, January 7, 2015

Reload stale objects in finnicky Rails rspec tests

I have had plenty of times where an rspec test was failing because an object was still showing an old value after an update. This seems to happen most often in Capybara. I know I'm not technically supposed to be checking object data in Capybara tests, just checking what's actually seen on the page, but there are some times where I need to be extra sure, or where the data being updated isn't showing on the page.

In rspec, call #reload on an object if it has changed in the database in order to sync and get the most recent changes.

NOTE: The object must have an ID, because under the hood it is just running
object = Object.find(object.id)

object.name # => 'Frank'
object.update(name: 'Tank')
object.name # => 'Frank'
object.reload
object.name # => 'Tank'

Tuesday, January 6, 2015

Adding error messages in custom Rails validate methods

If you've ever needed custom validators in Rails, this is what you need in your methods when something doesn't pass a validation:

  errors.add(:base, 'A star count score must be within the range of 0 to 5.')

This will add that error message to object.errors If you tweak it slightly to this, it becomes more extensible:

  errors.add(:star_count, 'score must be within the range of 0 to 5.')

You can call object.errors.full_messages.each do |message| and it will basically output this: “#{:star_count.to_s.capitalize} score must be within the range of 0 to 5.”