Feb 20, 2013
-
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
Oct 02, 2011
-
I recently ran into a problem on a client server where the msyqld process died, and neither of us noticed it for almost 24 hours. That meant that the site was completely useless that entire time. Needless to say, the client wasn't thrilled about this.
I told him that I'd look into setting up a monitoring service to, at the very least, notify me if msyqld ever crashed. During my research, I stumbled upon the god gem (which I'd used in the past to monitor mongrel processes back in the days before Passenger). I didn't realize it could be used to monitor other processes as well, such as msyqld.
God is a rubygem, so it's easy to install on any *nix operating system. It's config files are also written in Ruby, so that makes it even better.
What you'll need to get the script running:
-
Install the god gem (as root).
sudo gem install god
-
Find the location of your msyqld pid file.
mysqladmin -u root -p variables | grep pid_file
- Determine the command to start/stop/restart the msyqld service. Usually /etc/init.d/mysqld
Here is the config file I wrote to watch my mysqld process:
# run in non-daemonized mode (so you can monitor it) with `god -c /path/to/mysql.god -D`
# run normally with `god -c /path/to/mysql.god`
# Settings for email notifications (optional)
God::Contacts::Email.defaults do |d|
d.from_email = 'god@my-app.com'
d.from_name = 'God'
d.delivery_method = :smtp # this can also be :sendmail
d.server_host = 'smtp.myapp.com'
d.server_port = 25
d.server_auth = true
d.server_domain = 'myapp.com'
d.server_user = 'smtp_user@myapp.com'
d.server_password = 'password'
end
# you can create as many email entries as you'd like
God.contact(:email) do |c|
c.name = 'me'
c.to_email = 'me@email.com'
end
God.watch do |w|
# you can name this whatever you want
w.name = "mySQL Server"
# polling interval
w.interval = 30.seconds
# command to start service
w.start = "/etc/init.d/mysqld start && /etc/init.d/httpd restart"
# command to stop service
w.stop = "/etc/init.d/mysqld stop"
# command to restart service
w.restart = "/etc/init.d/mysqld restart && /etc/init.d/httpd restart"
# how long to wait after starting service before monitoring resumes
w.start_grace = 20.seconds
# how long to wait after restarting service before monitoring resumes
w.restart_grace = 20.seconds
# location of pid file
w.pid_file = "/var/run/mysqld/mysqld.pid"
# tell god to delete the pid file when mysqld crashes
w.behavior(:clean_pid_file)
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
end
# failsafe
on.condition(:tries) do |c|
c.times = 8
c.within = 2.minutes
c.transition = :start
end
end
# start if process is not running
w.transition(:up, :start) do |on|
on.condition(:process_exits) do |c|
# send an email to me to notify me that the service has crashed
c.notify = 'me'
end
end
# lifecycle
w.lifecycle do |on|
# If the service keeps triggering a restart over and over, it is considered to be "flapping".
on.condition(:flapping) do |c|
c.to_state = [:start, :restart]
c.times = 5
c.within = 1.minute
c.transition = :unmonitored
# If the service is flapping, wait 10 minutes, then try to start/restart again.
c.retry_in = 10.minutes
c.retry_times = 5
c.retry_within = 2.hours
end
end
end
A few things to note about my script:
- In my start and restart commands, I also restart Apache. Running my site with Passenger, I would get a Rack error when mysqld restarted, but apache hadn't. YMMV.
- I'm using an SMTP server for my email notification. You can also use sendmail, or you can exclude the email notifications altogether.
- When running the script for the first time, it's a good idea to add the -D flag. This runs god in non-daemonized mode so all output is piped to STDOUT. That way, you can watch what it's doing to ensure everything is working correctly.
For more information about the god gem, see their site: god.rubyforge.org
Tagged: mysqlgodruby
Sep 13, 2011
-
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 '/' => '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: rails3routes
May 25, 2010
-
Step 0: Vendor the sinatra gem (optional).
mkdir vendor
cd vendor
gem unpack sinatra
mv sinatra-* sinatra
Step 1. Create a config.ru Rack file in your project's root directory.
require 'rubygems'
require 'vendor/sinatra/lib/sinatra.rb'
set :public, File.expand_path(File.dirname(__FILE__) + '/public') # Include your public folder
set :views, File.expand_path(File.dirname(__FILE__) + '/views') # Include the views
set :environment, :production
disable :run, :reload
require 'app_file' # replace this with your sinatra app file
run Sinatra::Application
Step 2. Create a Capistrano deploy file.
This file should live at config/deploy.rb.
set :domain, "yourdomain.com"
set :application, "app_name"
set :deploy_to, "/var/www/apps/#{domain}"
set :user, "your_deploy_user"
set :use_sudo, false
set :scm, :git
set :repository, "git@github.com:you/application.git"
set :branch, 'master'
set :git_shallow_clone, 1
role :web, domain
role :app, domain
role :db, domain, :primary => true
set :deploy_via, :remote_cache
namespace :deploy do
task :start do ; end
task :stop do ; end
# Assumes you are using Passenger
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
end
task :finalize_update, :except => { :no_release => true } do
run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
# mkdir -p is making sure that the directories are there for some SCM's that don't save empty folders
run <<-CMD
rm -rf #{latest_release}/log &&
mkdir -p #{latest_release}/public &&
mkdir -p #{latest_release}/tmp &&
ln -s #{shared_path}/log #{latest_release}/log
CMD
if fetch(:normalize_asset_timestamps, true)
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = %w(images css).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
end
end
end
Step 3: Deploy the app.
cap deploy:setup
cap deploy
Step 4: Get a beer, you're done!
Tagged: capistranosinatra
Apr 24, 2010
-
Here is the presentation I gave at the April 2010 Nashville Ruby on Rails Meetup.
Tagged: capistranoruby on railsrails