s

Posts Tagged with "rails3"

Shoulda Matcher Model Extras

Feb 20, 2013  -  Comments

If you use RSpec with your Rails projects, chances are you also use shoulda_matchers (and if you don't, what are you doing with your life?!). You probably already know all about the basic model matchers, like the ones below.

# app/models/user.rb
class User < ActiveRecord::Base
  belongs_to :organization
  has_many :projects

  validates :name, :presence => true
  validates :email, :uniqueness => true
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'associations' do
    it { should belong_to(:organization) }
    it { should have_many(:projects) }
  end

  context 'validations' do
    it { should validate_presence_of(:name) }
    it { should validate_uniqueness_of(:email) }
  end
end

Association Extras

There are a ton of extra options you can use with the association matchers (pretty much any option you can pass to an association). Below are just a few.

# app/models/user.rb
class User < ActiveRecord::Base
  belongs_to :organization, :class_name => 'UserOrganization'
  has_many :contracts
  has_many :jobs, :through => :contracts
  has_many :projects, :order => 'date DESC', :dependent => :destroy

  accepts_nested_attributes_for :projects, :limit => 3
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'associations' do
    it { should belong_to(:organization).class_name('UserOrganization') }
    it { should have_many(:contracts) }
    it { should have_many(:jobs).through(:contracts) }
    it { should have_many(:projects).order('date DESC').dependent(:destroy) }
    it { should accept_nested_attributes_for(:projects).limit(3) }
  end
end

Validation Extras

There are even more options for use with the validation matchers. Here's a small sampling (including some mass assignment matchers).

# app/models/user.rb
class User < ActiveRecord::Base
  validates :name, :length => { :minimum => 10, :maximum => 100 }
  validates :email, :format => { :with => /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/ }
  validates :status, :inclusion => { :in => %w(active inactive suspended) }

  attr_accessible :name, :email
  attr_accessible :name, :email, :status, :as => :admin
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'validations' do
    it { should ensure_length_of(:name).is_at_least(10).is_at_most(100) }
    it { should validate_format_of(:email).with('user@email.com') }
    it { should validate_format_of(:email).not_with('user@email') }
    it { should ensure_inclusion_of(:status).in_array(['active', 'inactive', 'suspended']) }
  end

  context 'mass assignment' do
    it { should allow_mass_assignment_of(:name) }
    it { should allow_mass_assignment_of(:email) }
    it { should_not allow_mass_assignment_of(:status) }
    it { should allow_mass_assignment_of(:status).as(:admin) }
  end
end

These are just a few of the extras that shoulda_matchers offers. I would highly recommend that you read through the documentation to discover all the things you can do.

Tagged: rails3rspec

Using Wildcard Domains with Rails

Sep 13, 2011  -  Comments

Working on a recent project, I needed to allow users to set custom domains to point to their account pages on my server. They would set their domain when they registered, then set an A Record with their DNS to point to my server. The only problem was, I didn't know how to get Rails to do this.

The domain is already saved in the database, but I needed to tell the router to look for any incoming requests to a domain that doesn't match my primary domain (in this example, we'll say my primary domain is example.com). Thanks to Rails 3, I could just add a routing constraint (which can take a class as argument), like so

  MyApp::Application.routes.draw do
    
    # domain routes (THIS IS THE IMPORTANT PART)
    constraints(Domain) do
      match '/index.html' => 'user#show', :as => :user_domain
      
      resources :tasks
      resources :projects
      
      # any other routes that are domain-specific
    end
    
    # this needs to be below the "domain" section
    root :to => 'site#index'
    
  end

Notice that the root path is below the domain routes. This is important because it will get triggered before the domain routes otherwise (because routes are first come, first served). With these routes in place, it's as easy as finding the user by domain when we get a request.

We added the Domain class as a constraints argument, now we need to add that file. You can add the following domain.rb to your lib folder and make sure it's getting loaded on application boot.

  class Domain
    def self.matches?(request)
      request.domain.present? && request.domain != 'example.com'
    end
  end

It's as easy as that. We just check to make sure the domain is present and that it doesn't match our primary domain. This constraint will match any domain other than our primary domain.

As for server configuration, you have to tell your server to accept all incoming requests, regardless of domain. Below is a sample Apache virtualhost to accomplish this. Notice that we don't specify a ServerName or ServerAlias, we want it to match everything.

  <VirtualHost *:80>
    DocumentRoot /path/to/my_app/public

    <Directory /path/to/my_app/public>
      Options FollowSymLinks
      AllowOverride None
      Order allow,deny
      Allow from all
    </Directory>
  </VirtualHost>

IMPORTANT: You need to ensure that this virtualhost entry is loaded last. If you have any other virtualhosts that specify a ServerName, then they need to be loaded before this one. We're using this entry as a catch-all to route to our rails app. If you have all of your virtualhosts in one file, just put this entry last. If each entry is in its own file, just make sure this file is loaded last (you might add a zzz_ to the start of the filename to make sure).

Tagged: routesrails3