Turning off auto timestamping for testing in Rails
Suppose that you implemented a functionality that depends on values of created_at or updated_at fields of your models. How do you test it?
If you use fixtures that reside in test/fixture/*.yml files then there is no problem, because the values you set there for created_at and updated_at fields are saved to the database ‘as is’. So you can easily have an article created one week ago:
article: title: What a great day created_at: <%= 1.week.ago.to_s(:db) %> updated_at: <%= 1.week.ago.to_s(:db) %>
However, I don’t use fixtures files myself. I feel a bit dirty using them ;) I find fixture replacement tools far more maintainable. Namely, I love thoughtbot’s Factory Girl. But here comes the problem. This won’t work as expected with Factory Girl:
Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
That’s because ActiveRecord’s automatic timestamping feature sets Time.now for created_at and updated_at fields overriding our values. At least that’s ActiveRecord’s default behavior. Fortunately it can be disabled with:
Article.record_timestamps = false
Chances are that after creating a model with a custom timestamp we’ll want to turn automatic timestamping back on. But turning it off and on in many places in your unit tests would be pretty cumbersome. Wouldn’t it be cool if you could achieve all of this with a snippet below?
without_timestamping_of Article do Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago) end
It turns timestamping off, executes the block and turns timestamping back on. I find it clean and dry. Here’s the code to place in your test_helper.rb:
# test_helper.rb class Test::Unit::TestCase # or class ActiveSupport::TestCase in Rails 2.3.x def without_timestamping_of(*klasses) if block_given? klasses.delete_if { |klass| !klass.record_timestamps } klasses.each { |klass| klass.record_timestamps = false } begin yield ensure klasses.each { |klass| klass.record_timestamps = true } end end end end
Of course you can turn off timestamping for many models at once:
without_timestamping_of Article, Comment, User do Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago) Factory(:comment, :created_at => 1.day.ago) Factory(:user, :updated_at => 5.hours.ago) end
Hope you like it. If so, share :)
Dynamic cookie domains with Rack’s middleware
Handling sessions in multi-domain environment is not the simplest things to do, because of the fact that cookies are scoped to a domain they were set by.
Recently we were developing an application with such an idea in mind:
- Application will work as a base for other mini-applications (which we call sites)
- Each site can be accessed via different url types: site.example.org and example.org/site
- We want the users to remain logged in when switching from one url type to another
I won’t be covering application structure, routing, etc. here, I will only write about maintaing the sessions is such an environment.
So this is pretty simple here - all that we needed to do was to set cookie domain to .example.org (note the “dot” at the beginning). This could be done via:
ActionController::Base.session = { :domain => ".example.org" }
However there was an additional requirement that we need to deal with:
- Each site can be accessed via custom domain - site.com
- Of course there’s no way here to keep the user logged in when he’s switching from site.com to example.org/site or site.example.org, at least it cannot be done with setting cookie domain to whatever value
Technically, to access the site via site.com, that domain must point to our IP address. Then we need to detect that the site is being accessed via custom domain and set cookie domain respectively.
This could be done via some funky before_filters in an Application Controller, however we found much better and cleaner way.
Rack’s middleware to the rescue
Rack itself is a minimal interface between web server and your ruby framework. It’s used by Ruby on Rails (since 2.3) and Merb. The request comes from web server, goes through middleware layers and enters the application.
So we wrote a middleware layer that detects the host with which our application is accessed and sets cookie domain for the request. Here it is:
app/middlewares/set_cookie_domain.rb
class SetCookieDomain def initialize(app, default_domain) @app = app @default_domain = default_domain end def call(env) host = env["HTTP_HOST"].split(':').first env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{@default_domain}" @app.call(env) end def custom_domain?(host) domain = @default_domain.sub(/^\./, '') host !~ Regexp.new("#{domain}$", Regexp::IGNORECASE) end end
Now we need to turn it on:
environment.rb
config.load_paths += %W( #{RAILS_ROOT}/app/middlewares )
production.rb
config.middleware.use "SetCookieDomain", ".example.org"
.example.org is the default domain that will be used unless the application is accessed via custom domain (like site.com), we give it different values depending on environment (production/staging/development etc).
And since we’re fans of test driven development, here’s the test that ensures us that everything works as expected:
tests/integration/set_cookie_domain_test.rb
require 'test_helper' class SetCookieDomainTest < ActionController::IntegrationTest context "when accessing site at example.org" do setup do host! 'example.org' visit '/' end should "set cookie_domain to .example.org" do assert_equal '.example.org', @integration_session.controller.request.session_options[:domain] end end context "when accessing site at site.com" do setup do host! 'site.com' visit '/' end should "set cookie_domain to .site.com" do assert_equal '.site.com', @integration_session.controller.request.session_options[:domain] end end context "when accessing site at site.example.org" do setup do host! 'site.example.org' visit '/' end should "set cookie_domain to .example.org" do assert_equal '.example.org', @integration_session.controller.request.session_options[:domain] end end end
Test is sponsored by great Shoulda and Webrat gems.
Feel free to comment and share.
Websecurity through conventions and best practices
As promised few days ago, I publish materials from a presentation I gave at IT Underground Conference in Prague, Czech Republic.
Here’s a slideshow with full speaker’s notes. Spread the word ;)
Speaking at IT Underground Security Conference and Workshop in Prague
Tomorrow I will be speaking at IT Underground Security Conference and Workshop in Prague, Czech Republic. That is three day long event that starts today.
I will give a talk on “Websecurity through conventions and best practices” which will be focused on securing web applications from the most common attacks with examples from Ruby on Rails web development framework.
Expect to see materials from the conf here in few days.
Rails 2.2.2, Ajax and respond_to
As I wrote some time ago in the article about Rails, Ajax and jQuery, sometimes there are problems with Rails not interpreting correctly content type headers of ajax requests. It’s because not all web browsers send that header in the same way.
What I proposed was to sort the request.accepts array (array containing content type headers sent by browser) so that xml content type would be the first element. That would then trigger format.xml in our respond_to block.
However that approach does not work in Rails 2.2.2, because now the request.accepts array is frozen and it cannot be modified. I spent some time googling for the solution, but with no effect. So I dived into the API and Rails’ source code and came up with pretty nice and simple solution to the problem.
class ApplicationController < ActionController::Base before_filter :xhr_to_xml def xhr_to_xml request.format = :xml if request.xhr? end end
This piece of code is an equivalent of the snippet I proposed in the article I referred to at the beginning. Now all ajax request will trigger format.xml in respond_to blocks.
Materials from AIESEC Conference in Częstochowa, Poland
Last month the netguru team has been invited to Corporate Responsibility and Information Technology Conference organized by AIESEC (international student organization) which took place in Częstochowa, Poland. We went there and gave some talks on various topics and I think some of you may find materials from this conference quite interesting.
First, we led an unofficial discussion on “The (fading) differences between desktop and web applications”. We introduced the topic to our hearers and then let them express themselves, it was quite successful.
On Day 2, we organized Ruby on Rails workshops which consisted of “Introduction to RoR” talk and one-hour live-coding session during which a simple twitter like application has been developed. You can find the results on our Github account.
We finished Day 2 with a talk on Enterprise 2.0 and on Day 3 we talked about “Developing a web-development company (startup)”, which was quite different from what people heard on earlier talks gave by the representatives of big corporations. I have no materials to share on this, though. Sorry.
