Ramblings on Rails

A code poets journey through this technical wonderland.

HAML Is Not for ASSETS

development

So in my gem file I had gem 'haml' inside my assets block like this:

1
2
3
4
group :assets do
  gem 'haml'
  --- other gems here ---
end

This worked fine locally and all was well. Then I tried to push an update to AppFog and none of my HAML files loaded. Nothing showed up in the error log but there was an obvious problem. A site without any layout isn’t much of a site. I tried a few things but couldn’t get it to work so I bitched about it on Twitter:

1
2
Deff something up with @appfog not supporting haml. Works 
swimmingly on local but shits the bed after deploy. #frustrated

Yea, I was a little frustrated. So converted my files over to erb and left it at that. Then I was contacted via Twitter by Alex Parkinson from AppFog who asked me about the issue. This prompted me to look into it a bit more and right off the bat I noticed my issue with the gem file. I tried moving the HAML gem outside of the assets block and what do you know? That worked.

Thanks to GIT it was trivial to track down my previous files, merge in some changes I had made, and then push the latest up to AppFog and now all is well and I’m able to use HAML.

I apologized to AppFog via Twitter and also Tweeted about my mistake. I have to admit, I’m impressed Mr. Parkinson took the time to Tweet me about this. Had he not I likely would have just stuck with erb files and would have presumed that AppFog had an issue with HAML when all along it was my own ignorance that was screwing me up.

Counter Caching

developmentgemrails

As promised, a blog post detailing the proper way to handle your counter_cache columns. A bit of a recap before we delve into the new stuff: I have a need to know how many goals a user has and of those goals, how many are completed. So I have a users table and a goals table. The goals table has a user_id field and a completed field. I join the two models together with user has_many :goals and then goals belongs_to :user. From that we can query how many goals a user has, and then how many goals are completed. We can improve this a bit with eager loading however when using eager loading, we’re loading a lot more data than we really need. All we care about is the total count and the completed count. Loading titles, descriptions, and all the other data in the goals table is very inefficient.

So we decide to use a counter_cache column but discover that apparently the fine folks behind Rails don’t actually ever use this feature, or if they do, they are using it in some manner unbeknown to me as for my needs, I’m almost always going to need to know more than just one count on a particular model. Goals and completed goals or books and how many of those have been read or well, you get the idea.

After a bit of time spent googling and researching this I came across a gem that is, as far as I can tell, exactly what should be in the Rails code itself. This is how it should work by default since the norm, again just my opinion is multiple columns rather than just one. The gem is called counter_culture and if you have use for a counter_cache column, and I think a lot of you will, then you owe it to yourself to give this a try.

INSTALL

1
gem 'counter_culture', :git => 'https://github.com/magnusvk/counter_culture.git'

Then run bundle install to install the new gem.

DATABASE

The standard rails migration generator is only going to get you so far on setting up your new files. You can go ahead and generate the migration scaffold but then you’ll need to manually add some code to that. For my needs I used:

1
2
add_column :users, :goals_count,            :integer, :null => false, :default => 0
add_column :users, :goals_completed_count,  :integer, :null => false, :default => 0

We want the field(s) to be integers and for this to work properly, you need to set null to false and default them to 0. These settings are why the migration generator isn’t able to generate the entire thing for us. Once you have your migration setup, you’ll of course want to run rake db:migrate to get your new fields added.

CODE

Next you need to add some code to your model. This is as they say, where the magic happens.

1
2
3
4
5
6
7
belongs_to :user

counter_culture :user,
                :column_name => 'goals_count'

counter_culture :user,
                :column_name => Proc.new {|model| model.completed ? 'goals_completed_count' : nil}

The first line is our standard belongs_to and there’s nothing special about it here. Next we have my first counter_cache column being setup. This is the standard one that will give us the total count of goals for each user. We are calling counter_culture on the model and then giving it the column we want it to keep updated. Simple enough I think.

The next block sets up my other counter_cache column and this one is a bit more involved. It starts the same with setting up the model followed by the column name, but when we define the column name we’re using a Proc so that we can dynamically determine when the column needs to be incremented or decremented. In this case model.completed is a boolean and will return true or false depending on whether or not this goal is completed. If you have a more complex situation, you can instead do something like model.completed? and then def completed? however you want. Just keep in mind that your completed? method needs to return true or false and it should work fine.

DONE

So there you have it. Simple once you get the hang of it. I actually had some trouble getting this setup initially and received assistance from @magnusvk, the creator of the gem, who responded immediately and was quite helpful. I think you’d be hard pressed to find a friendlier community than what Rails has. People really seem to go out of their way to help each other and it’s because of that, Rails continues to thrive.

Rails Counter_cache Frustrations

developmentrails

For my bucket list site I need to know how many goals each user has and of those, how many are completed. Doing this with eager loading is easy but less than optimal. After watching the RailsCast on the subject (#23 Counter Cache Column - RailsCasts), I was intrigued. This seemed like the ideal way to efficiently solve my problem.

I quickly set things up and for total goals, it worked perfect. Create a new goal and the counter would increment. Delete a goal and the counter would decrement. Get the count for goals for a particular user and it would only hit the user table. Nice, clean, efficient. Great, I was half way home. Went to do the same thing for the second column, the counter for completed goals and ran into a problem. You apparently can’t have more than one counter cache column like this.

Normally you set up the counter cache column with the name and then _count so goals_count would be the name if you were getting the count for goals. I thought goals_count and goals_completed_count would be ideal but no dice.

After a bit of research I found a gem that does exactly what I want. I’m still in the process of implementing this gem but expect a future blog post on how to use this gem in the next few days.

Auto-Tweet Octopress Blog Posts Using IFTTT

blogging

When using a blogging platform such as Wordpress, you can typically find a plugin that will do pretty much anything. With Octopress you’re a bit more on your own. Thankfully there are services that assist with much of this. Today I setup an account on IFTTT which stands for If This, Then That. Once you have an account on here, setting up automatic tweets is trivial.

Easy Steps:

  1. Create a free account on IFTTT
  2. You’ll want to check your email and verify your email address by clicking the link in that email. I didn’t do this step initially and it took me a bit to figure out why my recipe wasn’t running.
  3. Click on Create a recipe
  4. Click on this (only option on this page)
  5. We’re going to be using our atom.xml file so click Feed
  6. Now click on New feed item so we get 1 tweet for each new blog post
  7. Feed URL is the path to our atom.xml such as: http://traffan.com/atom.xml
  8. Now click on that (only option on this page)
  9. Select Twitter since we are setting up a tweet.
  10. Here you’ll be asked to activate your Twitter account. This just means you are giving IFTTT access to post on your behalf. Don’t worry about this step. IFTTT is quite popular and well respected. They won’t do anything malicious.
  11. Now click on Post a tweet
  12. Now you need to tell it what you want your tweets to look like. I’m using: `New blog post: ’ for mine, but you can put whatever you want in yours.
  13. Last step asks you to enter the description you want to use for your new task and then click Create Recipe to save your new masterpiece.

Provided you set things up correctly and remembered to verify your email address, your new recipe should now be running every 15 minutes checking for new posts and tweeting about them.

Octopress Tapir

blogging

Note: You can find the relevant files on GitHub here: octopress-tapir

Tapir search for your Octopress blog!

Tapir works by indexing your RSS feed. Only what is included within this file will be indexed. For RSS purposes you most likely only want the last 20 or so articles but for search, you want all of them included. To handle this, we simply create a second xml file to use for search.

atom.xml

  • the original file
  • limited to 20 most recent posts
  • we use this for RSS

atom_search.xml

  • the new file
  • a modified version of atom.xml
  • includes all posts
  • added summary field

Install

  1. Visit Tapirgo.com and enter the url to your atom_search.xml like: http://yoursite.com/atom_search.xml
    After you enter your email and click the big GO button, you’ll be given both a public and a private token.
  2. Open your _config.yml and include the public token: tapir_token: your_id_here
  3. Copy loading.gif to your source/images/ folder.
  4. Copy jquery-tapir.js to your source/javascripts/ folder.
  5. Copy search.html to your source/ folder.
  6. Open source/_includes/navigation.html and add:
1
2
3
4
5
6
7
{% if site.tapir_token %}
  <form method="get" action="/search.html">
    <fieldset role="search">
      <input class="search" name="query" type="text" placeholder="Search..." x-webkit-speech />
    </fieldset>
  </form>
{% endif %}

That’s all there is to it. You should now be able to search your content using the wonderful Tapir service. :)

Gem: Consistency_fail

development,gems

In rails it’s common to add a validates_uniqueness_of validation to models for any fields that you want to be unique. Most of the time this is sufficient and will catch when a record is submitted that already exists. But what if two submissions come in at the same time? What if a user while registering clicks the submit button multiple times? You could run into a case where rails checks for uniqueness multiple times before the first record has time to be saved in the database, sees that they are in fact, at that point in time, unique, and allows them to go through. The database doesn’t know you want them to be unique so it gladly accepts them, and now your formerly pristine database is pristine no more.

Enter this nifty little gem: consistency_fail.

1
gem install consistency_fail
1
2
3
4
5
6
7
8
9
10
11
12
➜  bucketlist git:(master) ✗ consistency_fail

There are calls to validates_uniqueness_of that aren't backed by unique indexes.
--------------------------------------------------------------------------------
Model                      Table Columns
--------------------------------------------------------------------------------
ActsAsTaggableOn::Tag      tags (name)
ActsAsTaggableOn::Tagging  taggings (tag_id, taggable_type, taggable_id, context, tagger_id, tagger_type)
User                       users (username)
--------------------------------------------------------------------------------

Hooray! All calls to has_one are correctly backed by a unique index.

In my initial usage I had three indices that needed to be added. I quickly created two migrations (one for tags and taggings as they are related, and one for users) and then ran the tool again:

1
2
3
4
➜  bucketlist git:(master) ✗ consistency_fail
Hooray! All calls to validates_uniqueness_of are correctly backed by a unique index.

Hooray! All calls to has_one are correctly backed by a unique index.

Now sure, I know some of you will say that these indices should be created when you add the validation to the model and by doing that you don’t need a gem such as this one, but who among us is 100% effective in never forgetting little things like this? You create your table, build out a form, realize you want a couple of fields unique so you update your model, “oh, I need to add an index to the db… I’ll do that as soon as I’m done with…” and you forget all about it. We’ve all been there. But now we have consistency_fail to help us with such things.

Nested Models vs Monolithic

developmentrails

We start with a user table that contains the usual suspects. The user can login, enter their basic info, and even a blurb about themselves. Then they want to link their profile to various social sites. Simple enough, on the surface.

Beyond deciding which sites to support, we have to decide how we want to handle things on the back end. Do we want to go the proper router and employ nested models, or do we cram everything into the user table?

MonolithicNested
idid
emailemail
usernameusername
facebook 
flickr 
youtube 

With the monolithc setup, that’s all you need. If the user wants to link to Facebook, they enter thier Facebook username and you store in the field apropriately called Facebook. Simple, straightforward, and ultimately the fastest since it requires no joins in the database.

For the nested model method we’ll need to add two additional tables. One for our networks, and one for our associations between users and those networks.

NetworksUser_Networks
idid
nameuser_id
urlnetwork_id
 username

Nothing complex between these. Networks table has the name for each of our social sites and also the url to the profile page for each site. User_Networks has the association for each user to whichever social sites they’ve enabled and then their username for that site.

The problem happens when you go to setup all the code that is required to use nested models on the front end. You first set up your associations in the models and then you have to setup your nested attributes. Once that’s done and Rails is able to link the models together, you need to set thigns up in your controller and views. That’s where I ran into problems. I was able to use the cocoon gem and get things working fairly well, however while I was able to add social sites to my profile, I was NOT able to add the username for each one. I had a drop down list for the various social sites but couldn’t determine how to add an input box that would allow a user to enter their username. In the end I reverted back to the monolithic method.

As I stated earlier, the monolithic method has the bennefit of being faster. It’s impossible to get joins and associations to be as fast as having everything in one table. I don’t anticipate ever having thousands of concurrent users on this site, but in the event it gets popular, this decision here might really help me. Or at least that’s what I’m going to keep telling myself since I couldn’t get the other way working satisfactorily. :)

Rails Destroy Scaffold

development

Over the years I’ve generated a scaffold a time or two but until tonight the fact that you can reverse this with rails destroy scaffold ModelName had escaped me. Such a simple thing and quite the time saver. Combined with rake db:rollback, Rails makes it very easy to try new ideas and if they don’t work out, you can quickly reverse course.

Who Died and Left You Brew Master?

developmentfeatures

I’ve recently started a new project and I’m finding the most difficult aspect has nothing to do with the actual coding but instead, is deciding which features to include and/or how to include them. It’s the top-level stuff that is difficult, not the nitty gritty. You want to be able to login via Facebook? That’s an easy thing to add but with adding that comes the question of what other sites should we integrate with? Twitter? Google? Bob’s House of BBQ? I opted for the big three: Facebook, Twitter, and Google. And with Google came the question of do I include just Google or also Google+? What about Google Apps? Apparently these three all have different authentication methods.

When setting up the profile page I had a bunch of questions about what I should include. Do I include Gender? If so, what options do I want to have? Male, Female, Other? But other might offend some people. While considering this particular question I did a quick Google and found that there are actually a LOT of people that are offended by the term other in this context. So what then? And do I add an option saying they didn’t want to say? Or maybe I make it so you can hide your gender so others can’t see it.

So now we’re going to allow people to hide parts of their profile? Well, we also ask for first name and last name as well as each user having a username so we’ll want to allow them to hide their real names. And since we are asking their real names, do we want a way to verify their real names? I’ve seen that done on some sites.

And we want to allow people to list their Facebook and Twitter accounts but what about Google+? I don’t use it but I know a lot of people do. And Pinterest is popular. As is Instagram. As is… see where this leads? Maybe we set this up so it’s free form… they enter whatever site in one field and their username in a second field. But then how do we link it? Better to make it a drop down where they can select the ones they want.

So again, the most difficult aspect isn’t the actual code. I can write great code and add features until the cows come home, but knowing which features to include and which aren’t worth the time or even worse, will become problematic, that’s the difference between great sites and crap sites.

The Joys of Coffeescript

coffeescriptdevelopmentjquery

I just used Coffeescript for the first time. I’ve copied and pasted Coffeescript code before, of course, but this was the first time I had used it with my own code. And I’ll admit I cheated. I started with JQuery and then ran it through a converter (http://js2coffee.org/).

JQuery:

1
2
3
$("input[type='checkbox']#goal_completed").on('change', function(){
  $('.goal_completed_on').toggle();
});

Coffeescript:

1
2
$("input[type='checkbox']#goal_completed").on "change", ->
  $(".goal_completed_on").toggle()

For writing the code I was doing a simple <script> </script> block at the bottom of my _form.html.erb file but found that because I’m loading my JS files at the bottom of my layout, my JQuery wasn’t working. JQuery hadn’t loaded yet. Now that I have it as proper Coffeescript and in the propper assets file, assets/javascripts/goals.coffee.js in this case, everything is working great.