I found Camping first. It was hard to figure out because Google kept offering me places to vacation.
Then I saw Merb. I was too busy shaving yaks to ever give it a real go.
Now there’s Sinatra. It’s probably worth learning because it shouldn’t take more than 5 minutes.
I love that Ruby makes kickass websites in so many colors.
Thanks to DFisch for the heads up.
Tags:ruby
I figure it’s time I write a post with some guts to it. One thing I keep hearing people ask about is how to write code that extends Rails. Most of the books and tutorials out there (RailsCasts and PeepCode excluded) focus on getting you up and running but don’t show you how to really master your application.
So for folks who want to get better at Rails here’s a not uncommon situation involving external libraries and how to extend them cleanly. Using this technique you can easily add any kind of functionality to your program.
I’m finally using scope_out in an app and so far I’m loving it. I’d written some code that acted similarly to the scope_out plugin but it was not nearly as good. I’ve decided to replace that code with the plugin because the smaller I can get my own application code the better.
Scope out lets you define extensions to your normal finders. For example:
class Book < ActiveRecord::Base
scope_out :rated, :conditions => 'rating is not null'
end
That gives you:
Book.find_rated(:all) # == Book.find(:all, :conditions => 'rating is not null')
Book.calculate_rated(:count, :all) # == Book.calculate(:count, :all, :conditions => 'rating is not null')
Which is pretty handy. In fact it’s beautiful. It’s so good that Rick Olson even recommends it over his acts_as_paranoid plugin.
But my old code had the ability to count records like:
Book.count_rated
. I find myself doing this all the time. I suppose I could just replace my calls to
Book.count_rated
with
Book.calculate_rated(:count, :all)
but that would add an ugly level of complexity.
As I see it I have some options for how to get the counting functionality back in my app:
- I can submit a patch to the scope_out project to get my favorite method added to the code.
- I can manually edit my installation of the plugin.
- I can add a method_missing to ActiveRecord::Base that intercepts count_* and reformats the call to be used with scope_out
- I can add an extension to the scope_out plugin under my /lib directory that modifies the plugin without having to actually modify the plugin’s installed files.
So which is best?
#1 is the best idea but it’ll take a while and they might not accept it. Oddly enough I’m too lazy to submit a patch but I found time for this blog post.
#2 is a bad idea. I don’t want to support this plugin myself – I want the folks upstream to do all the work for me.
#3 might depend on the scope_out plugin depending how I write it. It seems like a clean solution otherwise.
#4 Kinda like #2 in that I’ll have to make sure my code is working fine every time I update the scope_out plugin. Too much work.
I’m going to go with #3. It’s the cleanest overall and will cause the fewest headaches down the road.
This kind of code could be made into a plugin but since I don’t plan to use this in any other apps yet I should start with the simplest option – which is to create a new file under ./lib and require it in my environment.rb file.
$ touch ./lib/scoped_count_extension.rb
$ echo "require 'scoped_count_extension'" >> ./config/envirionment.rb
Crafting the Ruby
If I wanted to be a total bum I could just do it like this:
class ActiveRecord::Base
def count_rated(*args)
calculate_rated(:count, :all, *args)
end
end
and that would kinda sorta solve my problem. Technically it would work and I could now use Book.count_rated(). But it would only work for the rated_scope – for any other scope I’d have to duplicate this code. And every agile developer I know would pummel me if I did that.
So I’m going to have to use method_missing.
class ActiveRecord::Base
def method_missing(method_called, *args, &block)
match = method_called.to_s.match(/^count_(\w+)$/)
if match
send "calculate_#{match[1]}", :count, :all, *args
end
end
end
Hmmm, it’s working but I think I’d rather only do the count -> calculate replacement if I know that with_scope is going to catch it. So I should check for with_rating or something like it because that’s a method that scope_out provides.
class ActiveRecord::Base
def method_missing(method_called, *args, &block)
match = method_called.to_s.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
end
end
end
There. That means that anytime a method gets called that looks like ‘count_something’ on any model class and there’s a ‘with_something’ defined (which means that scope_out is applied to this ‘something’) it’ll pass it along to calculate_something(:count, :all). The match() method is just performing a simple regular expression so I can capture the bit that comes after count_ and put calculate_ before it.
But now I’ve got a serious problem. Method_missing is a really handy method and it’s already used by ActiveRecord pretty heavily. So all I’ve really managed to do is break Rails. Things like Book.find_by_title rely on method_missing and I’d like to keep that behavior.
Thankfully Rails provides us with an incredibly useful tool called alias_method_chain. It’s a way to chain together a bunch of methods that all have the same name. As long as each one has a unique identifier they won’t clobber each other.
So I’ll alias_method_chain my method_missing:
class ActiveRecord::Base
def method_missing_with_scope_out_countable(method_called, *args, &block)
match = method_called.to_s.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
else
method_missing_without_scope_out_countable(method_called, *args, &block)
end
end
alias_method_chain :method_missing, :scope_out_countable
end
Much better. Now my code can work right alongside the rest of the Rails source. There’s just one more ‘gotcha’: this code reopens ActiveRecord::Base and just adds itself in there. This is affectionately called “monkey-patching” and it’s frowned upon when it can be avoided. So I’m going to rearrange this code to make it kinder to the class it’s modifying.
First I move this extended method_missing into a module of it’s own:
module ScopeOutCountable
def method_missing_with_scope_out_countable(method_called, *args, &block)
match = method_called.to_s.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
else
method_missing_without_scope_out_countable(method_called, *args, &block)
end
end
end
class ActiveRecord::Base
end
Then I move just the the stuff that I want to become part of ActiveRecord::Base into a submodule of it’s own:
module ScopeOutCountable
module ClassMethods
def method_missing_with_scope_out_countable(method_called, *args, &block)
match = method_called.to_s.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
else
method_missing_without_scope_out_countable(method_called, *args, &block)
end
end
end
end
class ActiveRecord::Base
end
Next I go ahead and include the top-level module into ActiveRecord:
module ScopeOutCountable
module ClassMethods
def method_missing_with_scope_out_countable(method_called, *args, &block)
match = method_str.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
else
method_missing_without_scope_out_countable(method_called, *args, &block)
end
end
end
end
class ActiveRecord::Base
end
Now, to get it actually working I’ll need to add an install hook that connects my method_missing to ActiveRecord::Base
module ScopeOutCountable
# included() gets executed anytime this module is included into an object. The object is passed as an argument to included().
class << self
def included(target)
target.extend ClassMethods
end
end
module ClassMethods
def method_missing_with_scope_out_countable(method_called, *args, &block)
match = method_str.match(/^count_(\w+)$/)
if match && respond_to?("with_#{match[1]}")
send "calculate_#{match[1]}", :count, :all, *args
else
method_missing_without_scope_out_countable(method_called, *args, &block)
end
end
end
end
class ActiveRecord::Base
include ScopeOutCountable
end
There. I’ve now got my custom method_missing call hooked up to a chain of other method_missings within ActiveRecord::Base. I can use it as simply as this:
Book.find_rated(:all) # == Book.find(:all, :conditions => 'rating is not null')
Book.calculate_rated(:count, :all) # == Book.calculate(:count, :all, :conditions => 'rating is not null')
Book.count_rated # == Book.count(:conditions => 'rating is not null')
And for those who are curious how this might be really easily turned into a plugin:
$ ruby ./script/generate plugin scope_out_countable
$ mv ./lib/scope_out_countable.rb /vendor/plugins/scope_out_countable/lib/scope_out_countable.rb
$ echo "require 'scope_out_countable'" > /vendor/plugins/scope_out_countable/init.rb
$ sed -i "s/require 'scope_out_countable'//" ./config/environment.rb # this is a lazy way of deleting the require line from environment.rb
Update: If anyone can explain to me why passing off to method_missing_without_scope_out_countable would completely bypass scope_out’s method_missing I’d love to hear about it.
Tags:ruby
Akhil Bansal recently wrote two excellent scripts for creating new Rails projects. The first was in bash (a sensible choice) and the second was a Ruby port of the same code. I've taken his code one step further and developed it into a Ruby program that's easier to modify to your own needs.
Take the following code, throw it on your system somewhere and run it. It'll set up a Rails app and take care of all the nutty details like ignoring log files and removing those pesky tmp and components directories from your repository.
#!/usr/bin/env ruby
require 'fileutils'
class LayRails
attr_accessor :path, :name, :svn_username, :svn_url, :svn_password
def initialize
setup && lay
end
def setup
unless system('rails -v') || system('rails.cmd -v')
puts "Cannot find rails. Terminating..."
exit 0
end
self.path = enter "Enter Rails Application Path:(eg: /home/akhil/ror): "
self.name = ARGV.first || path.split('/').last
self.svn_username = enter "Enter svn username: "
self.svn_password = enter "Enter the svn password: "
self.svn_url = enter "Enter the svn url: "
puts "
---------------------------------------
Please verify the following variables:
Svn Username: #{svn_username}
Svn URL: #{svn_url}
Svn Password: #{svn_password.gsub(/./, '*')}
Application Path: #{path}
Application name: #{name}"
while yes_no = enter("Proceed (y/n)").strip.upcase
return true if 'Y' == yes_no
if 'N' == yes_no
puts 'Terminating...'
exit 0
else
puts 'Please enter either a "y" or a "n"'
puts 'yes_no: '+yes_no.inspect
end
end
end
def lay
puts "Generating rails project: (#{path})"
@windows ?
psystem("rails.cmd #{f path}") :
psystem("rails #{f path}")
puts "SVNinitial import: "
puts "svn import #{path} #{svn_url}/trunk -m \"Initial Import\" --username #{svn_username} --password #{svn_password.gsub(/./, '*')}"
system "svn import #{path} #{svn_url}/trunk -m \"Initial Import\" --username #{svn_username} --password #{svn_password}"
FileUtils.remove_dir(path, true)
puts "Checking out from svn: "
psystem "svn checkout #{svn_url}/trunk #{path}"
FileUtils.cd(path, :verbose => true)
remove f('log/*'), 'Removing all log files from SVN'
ignore '*', 'log', 'Ignoring all log files under log dir'
remove 'tmp', 'Removing tmp directory from SVN'
remove 'components', 'Removing components directory from SVN'
ignore 'tmp', '.', 'Ignoring tmp dir'
puts "Generating optimized database file"
File.open(f('config/database.yml'), 'w') {|f| f.write(database_yml_file) }
puts "Moving database.yml to database.example"
psystem f('svn move config/database.yml config/database.example')
psystem 'svn commit -m "Moving database.yml to database.example to provide a template for anyone who checks out the code " '
ignore 'database.yml', 'config', 'Ignoring database.yml'
puts "Finished."
end
def remove(what, log)
psystem "svn remove #{what}"
commit log
end
def ignore(what, where, log)
psystem "svn propset svn:ignore '#{what}' #{where}"
psystem "svn update"
commit log
end
def commit(log)
psystem "svn commit -m '#{log}'"
end
def f(string)
@windows ||= RUBY_PLATFORM =~ /mswin/i
@windows ?
string.gsub('/', '\\') :
string
end
def psystem(string)
puts string
system string
end
def enter(string)
puts string
gets.strip
end
def database_yml_file
<
production:
<<: *credentials
development:
<<: *credentials
database: #{name}_development
test:
<<: *credentials
database: #{name}_test
DBYML
end
def credits
puts < - based on the work of Akhil Bansal"
CREDITS
end
end
# and away we go
LayRails.new
Tags:ruby
I think Amazon S3 is awesome. I was looking into building a RAID NAS (Network-Attached Storage) for backing up all my important data and I nearly bought a setup that would run into the hundreds of dollars - but then I did a little fancy multiplication and addition and realized S3 would cost me less than one one-hundredth what the NAT would have cost.
In case you’re as in the dark about S3 as I recently was, here’s a little rundown:
it’s a very simple, super fast, extremely large backup system that Amazon uses for all of it’s own storage needs. It’s opened the service up to the public on a pay-as-you-go basis.
Costs:
- $0.15 per GB-Month of storage used
- $0.20 per GB of data transferred
In other words, it’s damn cheap. Say you want to upload 500 MS Word documents that are around 45KB each? How much would it cost to store those on some easy-to-access, highly secure, permanent backup place? Less than one cent per month. Eight years later you’d only be out a half-dollar ($0.32 to be precise).
So S3 is my new storage/backup location of choice. The one difficulty of using it is that I need to figure out some way of automating the backups so that the backups are actually useful and can easily be recovered if necessary. In particular, I need some way to automatically backup the website data that is so crucial to me.
So I made a plugin.
The S3 plugin will allow you to backup your crucial website data to S3 via a handy Rake task (written by the talented Adam Greene).
Amazon has been an excellent supporter of Ruby/Rails lately (they fund 43things.com among other things) and they’ve made sure to release a ruby library for S3. I’ve combined that with Adam’s S3 rake task into a handy S3 backup plugin.
You can install it via the following two commands:
1
2
|
ruby script/plugin source http://svn.6brand.com/projects/plugins
ruby script/plugin install -x s3
|
Then backing up is easy as:
1
2
3
|
rake s3:backup:db
rake s3:backup:code
rake s3:backup:scm
|
or, to get them all together:
Tags:ruby
If you’re getting the title of this post as a strange error showing up in your rails app you’re not alone. If you happen to see this you’re probably running a new version of Ruby (1.8.5).
The source behind this error is in the syntax for Ruby’s control structures (particularly using case and if. Here’s an example of some code from the Rails trunk that follows the currently popular style:
active_record/base.rb:
def convert_number_column_value(value)
case value
when FalseClass: 0
when TrueClass: 1
when '': nil
else value
end
end
action_view/base.rb:
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
elsif erb_template_exists?(template_path): :rhtml
elsif builder_template_exists?(template_path): :rxml
elsif javascript_template_exists?(template_path): :rjs
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
end
end
If you’re getting the title of this post as a strange error showing up in your rails app you’re not alone. If you happen to see this you’re probably running a new version of Ruby (1.8.5).
The source behind this error is in the syntax for Ruby’s control structures (particularly using case and if. Here’s an example of some code from the Rails trunk that follows the currently popular style:
active_record/base.rb:
def convert_number_column_value(value)
case value
when FalseClass: 0
when TrueClass: 1
when '': nil
else value
end
end
action_view/base.rb:
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
elsif erb_template_exists?(template_path): :rhtml
elsif builder_template_exists?(template_path): :rxml
elsif javascript_template_exists?(template_path): :rjs
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
end
end
The warning comes from the use of colons to separate a condition from a statement. Apparently Ruby is working toward eliminating the use of this colon in favor of a semicolon. Since a semicolon marks the end of a line of code it already does what this colon is doing.
Here’s the code rewritten to use semicolons (so Ruby won’t barf out errors)
def convert_number_column_value(value)
case value
when FalseClass; 0
when TrueClass; 1
when ''; nil
else value
end
end
which is really just the same as:
def convert_number_column_value(value)
case value
when FalseClass then 0
when TrueClass then 1
when '' then nil
else value
end
end
and with newlines instead of “then”s:
def convert_number_column_value(value)
case value
when FalseClass
0
when TrueClass
1
when ''
nil
else value
end
end
and
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
elsif erb_template_exists?(template_path); :rhtml
elsif builder_template_exists?(template_path); :rxml
elsif javascript_template_exists?(template_path); :rjs
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
end
end
which is really:
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
elsif erb_template_exists?(template_path) then :rhtml
elsif builder_template_exists?(template_path) then :rxml
elsif javascript_template_exists?(template_path) then :rjs
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
end
end
and with newlines:
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
elsif erb_template_exists?(template_path)
:rhtml
elsif builder_template_exists?(template_path)
:rxml
elsif javascript_template_exists?(template_path)
:rjs
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
end
end
As is often the case with Ruby/Rails this is largely an aesthetic issue. You can see some discussion about it here: http://www.ruby-forum.com/topic/75349
I’m curious what you think, are the colons pretty enough to stay or should the Rails code be adopted?
Edit: Ryan’s right, I’ve been making some alterations to this code while the post was live – my apologies for the underhandedness. Also I’m totally sorry for my spazzy comments. Even if it gives you some weird error it probably saved just fine. I’m working on un-jacking it.
Edit: If you’re running some form of *x system (macs included) and you’re getting annoyed by the warning output theres a simple way to hide everything but the regular ruby output:
rake test:units 2> /dev/null
Tags:ruby
I launched JCFootballProspects.com a while ago and things have been going smoothly but the site is stuck with the nasty black-on-white error messages when somebody types in a wrong address.
I’m now at the point where I want to give out some fancy 404 pages and I’ve been looking around at what other folks have done. I’m amazed to find that all the examples I can find have a 404.html page in the `#{RAILS_ROOT}/public` directory. I’m sure it’s effective to just have a plain-html file that gets served up for errors (and it’s certainly a lot better than the default ugly stuff) but I’m convinced there are advantages to having application-provided 404 pages as well.
There are three different kinds of errors I trap and respond to:
* A user gives incorrect or insufficient parameters to a page view
* A user attempts to access an action that doesn’t exist
* A user attempts to access a controller that doesn’t exist
The first is easily handled – even the AWDWR book shows in an early example that you can output a flash message if something goes wrong with a request. The second is only slightly more complicated but, luckily, Ruby has a great way to respond to missing actions on controllers using method_missing. The solution to the third is provided by Rails’ routing capabilities.
How to hook up an app-driven 404 page:
====
The first thing I did was added a view to my default controller (mine is called ‘home’). I created ./app/views/home/404.rhtml and put just some basic stuff in it:
<%= content_tag 'h2', 'Whoops!' %>
<%= content_tag 'h3', 'Page not found' %>
[insert message here]
The next step was to add method_missing to my application controller:
1
2
3
4
5
6
7
8
9
10
11
|
class ApplicationController < ActionController::Base
...
def method_missing(methodname, *args)
@methodname = methodname
@args = args
render 'home/404', :status => 404
end
end |
And one super low priority route (put it at the very bottom) finishes off the job:
map.error ':controllername', :controller => 'home',
:action => '404'
Tags:ruby
I seem to have a limitless stream of excel files coming at me from clients. Most of them are of the same format: first line is the column name and the rest of the lines are data.
Ruby has excellent baked-in capabilities for handling files, data, and even CSV stuff. Still, it was pretty hard for me to figure out how to turn a CSV file into an array of hashes where each cell was named with the correct column name.
So here y’are folks: an easy way to turn CSV files into an array of hashes.
def csv_to_array(file_location)
csv = CSV::parse(File.open(file_location, 'r') {|f| f.read })
fields = csv.shift
csv.collect { |record| Hash[*(0..(fields.length - 1)).collect {|index| [fields[index],record[index].to_s] }.flatten ] }
end
Tags:ruby
(Note: this article might be better named “the most complicated database.yml file possible”)
Have you ever accidentally added your database.yml file to subversion or moved a database.yml file to a computer where it doesn’t belong? How about moving your dev server from your laptop to your desktop to your school’s web host to some new install of Suse you’re playing with?
Most of the time this is a big headache. You get errors like the one in the title where the socket was not found. Different flavors of *nix will put the default mysql socket in different places on the machine and it can be a pain to try to keep up with things.
Well, here’s a a database.yml file that can relieve you of ever having to remember which machine you’re on. Many thanks to LazyAtom go gave me the idea for this (who, in turn, got the idea from James Duncan Davidson
Behold – The Code:
login: &login
adapter: mysql
username: user
password:
socket: <%=
['/opt/local/var/run/mysql5/mysqld.sock', # darwinports
'/opt/local/var/run/mysqld/mysqld.sock', # darwinports, again
'/var/run/mysqld/mysqld.sock', # ubuntu/debian
'/tmp/mysql.sock'].select { |f| File.exist? f }.first %>
development:
<<: *login
database: rails_development
test:
<<: *login
database: rails_test
production:
<<: *login
database: rails
Ta-Da. I just installed this on my dev box and things are working great already.
For those of you who run a staging server to test your app in production, you might want to check out the following setup (I’m seriously considering a staging server setup for my apps. Also, this is good if you’re absolutely dead-set on using the same database.yml file for multiple hosts and multiple configurations (just as long as you know the hostname).
<% host = `uname -n`.strip %>
user: &user
<% if host.eql? 'staging' %>
username: staging_user
password: staging_pass
<% else %>
username: user
password: pass
<% end %>
login: &login
<<: *user
adapter: mysql
socket: <%=
['/opt/local/var/run/mysql5/mysqld.sock', # darwinports
'/opt/local/var/run/mysqld/mysqld.sock', # darwinports, again
'/var/run/mysqld/mysqld.sock', # ubuntu/debian
'/tmp/mysql.sock']
.select { |f| File.exist? f }.first %>
development:
<<: *login
database: rails_development
test:
<<: *login
database: rails_test
production:
<<: *login
database: rails
If you want to partition the login info for several different users on a large collaborative project you may want to have a special file (ignored by SVN) that holds just the login info for a given user:
login: &login
adapter: mysql
username: user
password:
socket: <%=
['/opt/local/var/run/mysql5/mysqld.sock', # darwinports
'/opt/local/var/run/mysqld/mysqld.sock', # darwinports, again
'/var/run/mysqld/mysqld.sock', # ubuntu/debian
'/tmp/mysql.sock'].select { |f| File.exist? f }.first %>
# replace the above login details with information from ./config/dblogin.yml
<%= file = File.join(RAILS_ROOT, "config", "dblogin.yml")
IO.read(file) if File.exist?(file) %>
development:
<<: *login
database: rails_development
test:
<<: *login
database: rails_test
production:
<<: *login
database: rails
Tags:ruby
I decided to try filling up my ./test/integration folder today because it was empty and I felt I must have been missing out. I can’t stand the idea that all the other Rails devs are enjoying some feature I haven’t found out about yet.
Integration tests seem pretty handy because functional tests are pretty basic – effectively unit tests for controllers rather than tests of real interactivity. So I figured I’d google around a bit to discover this integration testing thing and this is what I found:
(which currently doesn’t have a web server so I had to grab the page from Google’s cache).
That’s it. Those three meager pages are all the info I could find on Rails integration testing. In fact, I have reason to believe that the Rails Recipes book just re-used the code from Jamis Buck’s post – so even they don’t have any more for us.
Well, that’s not acceptable. I’m trying to follow a particular user’s experience through my app and I want to get this working without having to resort to SeleniumOnRails or some other browser-emulator.
My code so far
I’ve got a particular user type `Athlete` that logs in and checks their profile. They update a couple pieces of information and we check that the info was saved. Should be easy. (note: to understand this code check out Jamis’s article – you might need to look in the Google Cache for it)
class AthleteSessionTest < ActionController::IntegrationTest
fixtures :users,
:athlete_contacts,
:coach_contacts,
:junior_colleges,
:combines
def test_randy_visits
randy = new_session_as('Cliffton Player')
randy.goes_to_profile_edit
randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
randy.
end
private
module AthleteSessionDSL
def goes_to(place)
get place
end
def goes_to_login
get '/auth/login'
assert_response :success
assert_template 'auth/login'
end
def goes_to_profile_edit
get '/athletes/profile_edit'
assert_response :success
assert_template 'athletes/profile_edit'
end
def logs_in_as(athlete)
@current_user = users(athlete)
post 'auth/login', :user => {:email => @current_user.email, :password => @current_user.password }
assert_response :redirect
follow_redirect!
assert_template 'athletes/profile'
end
def updates_profile_with(contact)
post 'athletes/update_contact', :contact => contact,
:athlete_id => @current_user.id
assert_flash_equal 'This profile has been updated with new information', :success
assert_response :redirect
follow_redirect!
assert_template 'athletes/profile'
contact.each { |k,v| assert_equal v, @current_user.profile.attributes[k.to_s] }
end
end
end
def new_session
open_session do |sess|
sess.extend(AthleteSessionDSL)
yield sess if block_given?
end
end
def new_session_as(person)
new_session do |sess|
sess.goes_to_login
sess.logs_in_as(person)
yield sess if block_given?
end
end
end
The Problem
So this is fine. So far it’s working great. The problem is that I’ve set it up so that everything my user does is part of my AthleteSessionDSL module in it’s own method. Everything `randy` does has to be a method within the `AthleteSessionDSL` module.
I wanted to try some non-modular navigation to give Randy a bit more of a free spirit. I thought I’d see if I could take some of the functionality out of the module and do it directly:
def test_randy_visits
randy = new_session_as('Cliffton Player')
randy.goes_to_profile_edit
randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
# trying some direct session navigation outside of the randy session object
get 'athletes/profile'
assert_response :success
assert_template 'athletes/profile'
end
What happened? Well, since `randy` is really just an instance of a Rails session with some handy methods attached to it I immediately left the session when I stopped using `randy`. My request to get the “athletes/profile” page resulted in a redirect to my login page because that method is authentication protected.
Well, what’s the solution? Do I have to make a million methods for everything I want Randy to do?
The Solution
It turns out to be quite simple. The methods we have been using like `assert_response` and `get` (and all the rest) are, in fact, methods of the session. The `open_session` call in the `new_session` module creates a session object with all the standard methods on it.
So not only can we group functionality into methods and roll those into included modules – we can also call things directly:
def test_randy_visits
randy = new_session_as('Cliffton Player')
randy.goes_to_profile_edit
randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
randy.get '/athletes/profile'
randy.assert_response :success
randy.assert_template 'athletes/profile'
end
Tags:ruby
Sometimes it’s hard to come by this list when you need it. Hope this saves somebody some time. Here are the 50 states with their two-letter abbreviations in a Ruby-formatted array.
1
2
3
4
5
6
7
8
9
10
11
|
[ ["ALABAMA","AL"],
["ALASKA","AK"],
["AMERICAN SAMOA","AS"],
["ARIZONA ","AZ"],
["ARKANSAS","AR"],
["CALIFORNIA ","CA"],
["COLORADO ","CO"],
["CONNECTICUT","CT"],
["DELAWARE","DE"],
["DISTRICT OF COLUMBIA","DC"],
["FEDERATED STATES OF MICRONESIA","FM"], |
["FLORIDA","FL"],
["GEORGIA","GA"],
["GUAM","GU"],
["HAWAII","HI"],
["IDAHO","ID"],
["ILLINOIS","IL"],
["INDIANA","IN"],
["IOWA","IA"],
["KANSAS","KS"],
["KENTUCKY","KY"],
["LOUISIANA","LA"],
["MAINE","ME"],
["MARSHALL ISLANDS","MH"],
["MARYLAND","MD"],
["MASSACHUSETTS","MA"],
["MICHIGAN","MI"],
["MINNESOTA","MN"],
["MISSISSIPPI","MS"],
["MISSOURI","MO"],
["MONTANA","MT"],
["NEBRASKA","NE"],
["NEVADA","NV"],
["NEW HAMPSHIRE","NH"],
["NEW JERSEY","NJ"],
["NEW MEXICO","NM"],
["NEW YORK","NY"],
["NORTH CAROLINA","NC"],
["NORTH DAKOTA","ND"],
["NORTHERN MARIANA ISLANDS","MP"],
["OHIO","OH"],
["OKLAHOMA","OK"],
["OREGON","OR"],
["PALAU","PW"],
["PENNSYLVANIA","PA"],
["PUERTO RICO","PR"],
["RHODE ISLAND","RI"],
["SOUTH CAROLINA","SC"],
["SOUTH DAKOTA","SD"],
["TENNESSEE","TN"],
["TEXAS","TX"],
["UTAH","UT"],
["VERMONT","VT"],
["VIRGIN ISLANDS","VI"],
["VIRGINIA ","VA"],
["WASHINGTON","WA"],
["WEST VIRGINIA","WV"],
["WISCONSIN","WI"],
["WYOMING","WY"] ]
Tags:ruby