Sending iOS push notifications from a Rails application is very easy to do these days, thankfully there are many great Ruby gems that can be used to handle most of the magic for you. Recently I ran into the apn_sender gem which handles sending push notifications in a really neat way.
Sending push notifications directly from a Rails application can be slow and we probably don’t want to have the user waiting until the notification is sent, instead apn_sender can be setup to run a worker which is constantly connected to the apple push notification service. When there are new notifications to send, the notifications are queued up and sent through the always open connection that is maintained by the worker.
apn_sender uses redis as a message queue to keep track of the notifications waiting to be sent, you’ll need to install it before using the gem.
To add apn_sender in your Rails 3 application, just add the gem to your Gemfile. We’re going to need the daemons gem too so we’ll include that as well.
Now we can create our daemon which we will be using for sending push notifications, this can be placed anywhere, I’ve put mine in script/apn_sender. Make sure to add execute permission to the file after creating so we can run it.
Before the daemon can start running we’ll need to put our iOS push certificate into the application. Instructions for generating the certificates are available at the Apple Developer site. The certificates need to be placed inside of /config/certs and should be named apn_development.pem or apn_production.pem for production.
Once the certificates are in their correct locations, we can start up the daemon. The daemon does not know about the Rails environment so we need to specify this when starting it up. The daemon supports start, stop and restart commands. There is a verbose flag available to output more information (which can be helpful when debugging).
Our application is now set up to send push notifications, this can now easily be performed by adding a new notification to the queue. The notify method on the APN class will take a push notification token and then our parameters, we can specify the alert message to show the user, whether or not we want sound as well as the number to display on the badge icon. Anything else we pass to notify will be sent as metadata in the push notification. Here’s an example of creating a notification.
The worker should pick up the notification within a few seconds and send it off. The apn_sender has many other features that I haven’t covered, you can view the full documentation over at https://github.com/kdonovan/apn_sender.
Today I had some knowledge delivered straight to the top of my dome, by Darcy. The issue was I had two models that I wanted to join and then apply scopes to. I wanted to retrieve Story objects that were joined to HourEntry objects, that were from this week. However when I tried to use the Time scope on the query it was attempting to call those methods on Story.
ree-1.8.7-2011.02 :132 > Story.joins(:hour_entries).this_week
NoMethodError: undefined method `this_week' for #
The problem was that the .this_week call was being sent to Story rather than to HourEntry. Here’s where Darcy chimed in with the scoped.merge call. It went a little something like this :
Story.scoped.merge HourEntry.this_week
This successfully merged the scope defined by Story with the scope defined by the HourEntry.this_week call and I get all the stories that have HourEntry objects that are returned by the .this_week scope.
This then returns all the stories you’ve worked on this week. Add a bit of grouping/distinct and it’s done and done. The additional nice part about this is composing these scopes will allow me to do a lot more in the future, very quickly.
There was a question that was asked on our IRC channel today about how to get all the associations for an Activerecord model. I’m assuming it was to do some debugging or something, in any case I did a bit of digging around in the Rails docs and it turns out the answer isn’t that hard.
Doing something like :
Model.reflect_on_all_associations
Will give you all the associations for that model, it’s pretty dirty though and so an easy way to tidy it up is :
I had an issue whereby a label I thought was fine wasn’t being picked up on my Cucumber tests. It turned out that the test environment was of course running our compiled javascript and I hadn’t compiled it since my change. The labels didn’t have the ‘for’ attribute specified so Selenium was whinging it couldn’t find the element I wanted to find.
In any case I couldn’t find an explanation of how to get Firebug working with Selenium and Capybara but after some doc reading I got my solution working so I thought I’d post it here.
In my env.rb file I have :
Capybara.register_driver :selenium do |app|
Capybara::Driver::Selenium
profile = Selenium::WebDriver::Firefox::Profile.new
profile.add_extension(File.expand_path("features/support/firebug.xpi"))
Capybara::Driver::Selenium.new(app, { :browser => :firefox, :profile => profile })
end
Today I was fiddling with some code to get particular types of payments that are due on particular days and I ran across a couple of things I don’t want to solve again.
Firstly, the problem of being able to have default arguments to a block in ruby. It’s solved nicely in Ruby 1.9 but we’re using 1.8.x on our boxes at the moment. The work around is incredibly simple though and goes something like this :
That’s all there is to it really. You could go the whole hog with hashed attributes and so on but I think it starts to get a bit smelly if your anonymous functions are taking more than one argument.
The other issue I had was whether a named scope can include a join, and it can.
The coolest thing about all of this though (and I feel I’m very late to the party here) is that I now get to do things like :
Payment.credit_card.due_on(Date.today)
# or
Payment.credit_card.due_on
# or
Payment.due_on(Date.today + 1.month)
In the above I had just used the default argument to the due_on named scope to be today’s date.
I mean, I’d seen a bunch of tutorials on named scopes and how they worked but hadn’t found a use case in my work. I think it’s finally twigged for me though and will be making use of them a bit more in the future.