Thursday, July 17, 2014

Add Twitter cards to your Rails site with metamagic

Lately I've been working on adding support for Twitter Cards to the Ztory website. Basically it's just a bunch of metatags that need to be added to your page, so that the next time you tweet with a link to your site you get something like this:


However, there were some tricky parts in the process, so I want to document my efforts here, as a small how-to guide.

To add the card you see above to our site, I used the twitter documentation for the large image summary card, and I also added some metatags mentioned on the markup reference, which can be used on all twitter cards (twitter:site, twitter:app:id and twitter:app:url ).

Doing this with the metamagic gem is very simple, after adding it to my Gemfile, I just needed to add the following to app/views/magazines/show.html.erb, for the magazine-specific metatags:
<%
twitter card: 'summary_large_image',
          description: magazine.description,
          image: magazine.latest_issue..image_url,
          title: magazine.name,
          app: {
            url: {
              iphone: "ztory://view/magazines?magazine_id=#{magazine.id}",
              ipad: "ztory://view/magazines?magazine_id=#{magazine.id}",
              googleplay: "ztory://view/magazines?magazine_id=#{magazine.id}"
            }
          }
%>
And then the following on the <head> section of my layout, for the metatags that will be shared on all pages:
<%
  twitter site: '@ztoryapp'
          app: {
            id: {
              iphone: '647302038',
              ipad: '647302038',
              googleplay: 'com.ztory.main'
            },
            country: 'se'
          }
%>
<%= metamagic %>

(Be sure that you add the metamagic call *after* the calls to the twitter method, or else it won't work. I spent a good 20 minutes on this :P)
After making sure that the metatags were being rendered properly, I deployed and headed to the Twitter Card Validator page. There, I inputted my URL with the card metadata, checked that the metatags were correct, and submitted the domain for validation:
























Twitter said that it could take a couple of weeks to get an answer, but thankfully I got one in just a few minutes :D 

After that, I tried making a test tweet, and everything worked perfectly! The best part of it, is that if you check the tweet on your mobile device, you can click a button to open that magazine in the Ztory app directly:





Hope it works for you as well as it did for me! :)


Monday, July 14, 2014

Wait for Javascript and AJAX changes in Capybara

Recently I was having trouble with JS-enabled tests with Capybara on a Ruby on Rails app, when clicking stuff around and then asserting about content that was not on the page yet. I tried several solutions I found online, but either they were talking about wait_until, which is a method that's not available in Capybara anymore, or they just didn't work for me. Therefore, I decided to roll my own:
def wait_for
  Timeout.timeout(Capybara.default_wait_time) do
    loop until
      begin
        yield
      rescue MiniTest::Assertion
      end
  end
  yield
end
What this method does is wait for a few seconds for the assertions in the provided code block to run sucessfully. I use it by calling it with the assertion(s) I want to check on the page after a Javascript-related change, like so:
visit frontend_magazine_path(magazine_id: @magazine.id)

click_link 'Read latest issue'

wait_for do
  assert_equal "20 min left to read", page.find('#reader5-timer').text
end
Right now it's working pretty well, I was able to remove those pesky sleep n calls, and my tests feel sturdier and cleaner now.

Friday, June 27, 2014

Debugging I18n lookup in Rails

Here's a nice trick to debug I18n translation strings on Rails.

1. Add this to config/initializers/i18n.rb:
module I18n
  module Backend
    class Simple
      # Monkey-patch-in localization debugging
      # Enable with ENV['I18N_DEBUG']=1 on the command line in server startup, or ./config/environments/*.rb file.
      #
      def lookup(locale, key, scope = [], options = {})
        init_translations unless initialized?
        keys = I18n.normalize_keys(locale, key, scope, options[:separator])

        puts "I18N keys: #{keys}"

        keys.inject(translations) do |result, _key|
          _key = _key.to_sym
          return nil unless result.is_a?(Hash) && result.has_key?(_key)
          result = result[_key]
          result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)

          puts "\t\t => " + result.to_s + "\n" if (result.class == String)

          result
        end
      end
    end
  end
end if ENV['I18N_DEBUG']

2. Call your server/test suite from the console with the environment variable I18N_DEBUG=1, and you'll get this nice log of where Rails is looking for your translation strings:
$ I18N_DEBUG=1 rake test TEST=test/integration/frontend/sign_up_test.rb
Run options: --seed 27950

# Running tests:

I18N keys: [:sv, :frontend, :home, :index, :sign_in, :sign_in]
   => Logga in
I18N keys: [:sv, :frontend, :home, :index, :sign_up, :facebook_connect]
   => Logga in med Facebook
I18N keys: [:sv, :frontend, :home, :index, :sign_in, :or]
   => eller
I18N keys: [:sv, :frontend, :home, :index, :sign_in, :email]
   => Email
...

Finished tests in 2.280371s, 0.8771 tests/s, 3.5082 assertions/s.

2 tests, 8 assertions, 0 failures, 0 errors, 0 skips
I'm reposting this code (with some modifications) from heresince that link doesn't seem to work most of the time.

There's also another method explained in this blog post for logging all missing translations and fallbacks, which also seems very useful. Maybe I'll try it next time :)

Have a nice day!

Wednesday, June 18, 2014

Disable dangerous rake tasks in production

Yesterday, we had to restore the production DB from a backup, since we ran "rake db:schema:load" in production by mistake. To avoid that problem in the future, I decided to disable that task, and others than can screw with the DB, in production. 

It was a matter of adding a prerequisite to those dangerous tasks, that checks if they are being run in production, and exit accordingly. I also added a flag to override this safeguard, together with some code to backup the DB:
DISABLED_TASKS = [
  'db:drop',
  'db:migrate:reset',
  'db:schema:load',
  'db:seed',
  # ...
]

namespace :db do
  desc "Disable a task in production environment"
  task :guard_for_production do
    if Rails.env.production?
      if ENV['I_KNOW_THIS_MAY_SCREW_THE_DB'] != "1"
        puts 'This task is disabled in production.'
        puts 'If you really want to run it, call it again with `I_KNOW_THIS_MAY_SCREW_THE_DB=1`'
        exit
      else
        require 'heroku'
        puts 'Making a backup of the database, just in case...'
        puts `heroku pgbackups:capture`
      end
    end
  end
end

DISABLED_TASKS.each do |task|
  Rake::Task[task].enhance ['db:guard_for_production']
end
It would be nice to add something like this to the default Rails app, since, if you are reading this, chances are high that is too late for this to protect you by now :)

Sunday, June 1, 2014

Disable long press on Mac OS X for vim-mode in Atom editor

I've been using Github's Atom editor for a while now, and one of the first things I tried is vim-mode. However, the Mac OS X special characters popup when pressing a key for a few seconds interfered with my usage, since I could not press "j" or any other key repeatedly by long pressing it. 

To disable this behaviour, on your terminal, write the following:

defaults write com.github.atom ApplePressAndHoldEnabled -bool false

And restart Atom, just to be sure.

Happy editing!

Wednesday, December 18, 2013

Replay network requests on your tests: VCR.py

Today I'm happy to present you another library to aid with testing, this time related to tests that need network conectivity: VCR.py

VCR.py, a Python version of Ruby's VCR, works by recording the HTTP requests and responses your test makes, and then replays them when the tests is run again, thus achieving network independence. As a side effect, it also makes the test much faster, which is always nice.

Using it on a typical test is very easy, you just need to import it, and then surround the test that will interact with the network with a with vcr.cassete() block:
def test_colors_from_mapi(self):
    with vcr.use_cassette('tests/assets/vcr_cassettes/'\
            'color_mismatches-colors_from_mapi.yaml'):
        expected_result = set(self.mapi_colors)
        actual_result = color_mismatches.colors_from_mapi()

        self.assertEqual(expected_result, actual_result)

Here, I had a test that was making a request to an API, and building a color list with it. By adding the vcr block, and running the test once, a cassette in the path I specified was created with the contents of the HTTP interaction. When I run the test again, I could see by the speed that it was not making the request anymore, but working straight from the cassette.

You can find many configuration options on the repo for the library, I will not explain them here because I didn't even need them, which tells a lot about the simplicity of the tool ;)

This is yet another time when I find a library in Python doing just what I needed compared to when I was coding in Ruby (remember freezegun?). The switch between both languages is being much easier than I expected :)


Monday, December 16, 2013

Parse ISO 8601 dates in Python

Quick question-answer for today:

Q: How to parse an ISO 8601 date in Python?

A: Use dateutils Use the iso8601 module Use strptime:

*time.strptime(my_date,"%Y-%m-%dT%H:%M:%S")[:6]
I spent quite some time looking for an easy answer to this, but all the suggested modules I got where either buggy and not maintained anymore, or so old so that they were not even available on pip. So, this one liner was the best solution in the end.

PD: Beware of timezones! This works assuming you're using UTC.