I am new to ruby on rails and I have been reading the Agile web development with rails book.
I am working on Iteration B1: Validating, and I am really confused by the symbol :product while I am working on the test. The question is about the ":product => #update"
I really have no idea what does this mean and where the :product symbol come from. I know it is a hash, but which table does it hash to? what exactly does it do here? The code is as following. Thank you in advance.
require 'test_helper'
class ProductsControllerTest < ActionController::TestCase
setup do
#product = products(:one)
#update = {
:title => 'Lorem Ipsum',
:description => 'Wibbles are fun!',
:image_url => 'lorem.jpg',
:price => 19.95
}
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:products)
end
test "should get new" do
get :new
assert_response :success
end
test "should create product" do
assert_difference('Product.count') do
***post :create, :product => #update***
end
assert_redirected_to product_path(assigns(:product))
end
# ...
test "should show product" do
get :show, :id => #product.to_param
assert_response :success
end
test "should get edit" do
get :edit, :id => #product.to_param
assert_response :success
end
test "should update product" do
put :update, :id => #product.to_param, :product => #update
assert_redirected_to product_path(assigns(:product))
end
# ...
test "should destroy product" do
assert_difference('Product.count', -1) do
delete :destroy, :id => #product.to_param
end
assert_redirected_to products_path
end
end
There is nothing magical about this construct. The #update is a variable name that references a hash. The hash is declared earlier in your test file.
#update = {
:title => 'Lorem Ipsum',
:description => 'Wibbles are fun!',
:image_url => 'lorem.jpg',
:price => 19.95
}
This hash contains new data that should be passed to the update action in the products controller. It's confusing because of the way the variable is named. A better name might help:
#product_attributes
The products#update action expects a hash containing updated data. That data is used to update the object.
The following line in your test...
post :update, :product => #update
Corresponds to this line you probably have in your products controller:
if #product.update_attributes(params[:product])
Notice params[:product]. It's basically saying "make a post request to products#update and pass it the #update hash as :product.
And that explains the symbol part. The :product symbol in your test is the name of the parameter that contains the product data, that the update action expects.
post :update, :product => #update
In theory you can call it whatever you want. But per convention it helps to call it the resource name.
I am creating a Rails app (fairly new at this), and I have a Client model that I generated without using scaffolding. I have run 'rake db:migrate', and I am unit testing the model with 'rake test:units', however I am getting variations of the following runtime error in terminal for practically all of my factory testing.
test: Creating seven clients should show that all factories are properly created. (ClientTest):
NoMethodError: undefined method `destroy' for nil:NilClass
/Users/myUserName/Desktop/app_name/test/unit/client_test.rb:131:in `block (2 levels) in <class:ClientTest>'
I have been unable to figure out what the error is. I understand that it doesn't recognize the client class and thus it can't find the 'destroy' method for nil. However, I'm unsure of how to fix it.
Below is my model code, located in app_name/app/models/client.rb
class Client < ActiveRecord::Base
# Callbacks
before_save :reformat_phone
# Relationships
has_many :assignments
has_many :counselors, :through => :assignments
has_many :interventions, :through => :assignments
# Validations
validates_presence_of :first_name, :last_name, :gender, :address, :city, :state, :zip, :phone, :active
validates_inclusion_of :gender, :in => %w[male female], :message => "is not an option"
validates_inclusion_of :marital_status, :in => %w[single married separated divorced], :message => "is not an option"
validates_inclusion_of :state, :in => %w[PA OH WV], :message => "is not an option"
validates_format_of :zip, :with => /^\d{5}$/, :message => "should be five digits long"
validates_format_of :phone, :with => /^\(?\d{3}\)?[-. ]?\d{3}[-.]?\d{4}$/, :message => "should be 10 digits (area code needed) and delimited with dashes only"
# Scopes
scope :active, where('active = ?', true)
scope :inactive, where('active = ?', false)
scope :alphabetical, order('last_name, first_name')
scope :receiving_gov_assistance, where('gov_assistance = ?', true)
scope :not_receiving_gov_assistance, where('gov_assistance = ?', false)
scope :male, where('gender = ?', 'male')
scope :female, where('gender = ?', 'female')
scope :by_marital_status, lambda { |status| where("marital_status = ?", status) }
scope :by_ethnicity, lambda { |race| where("ethnicity = ?", race) }
scope :employed, where('is_employed = ?', true)
scope :unemployed, where('is_employed = ?', false)
scope :veteran, where('is_veteran = ?', true)
scope :assigned, where('current_assignment != ?', nil)
scope :unassigned, where('current_assignment = ?', nil)
# Other methods
def name
"#{last_name}, #{first_name}"
end
def proper_name
"#{first_name} #{last_name}"
end
def current_assignment
curr_assignment = self.assignments.select{|a| a.end_date.nil?}
# alternative method for finding current assignment is to use scope 'current' in assignments:
# curr_assignment = self.assignments.current # will also return an array of current assignments
return nil if curr_assignment.empty?
curr_assignment.first # return as a single object, not an array
end
# Misc Constants
GENDER_LIST = [['Male', 'male'],['Female', 'female']]
STATES_LIST = [['Ohio', 'OH'],['Pennsylvania', 'PA'],['West Virginia', 'WV']]
# Callback code
# -----------------------------
private
def reformat_phone
phone = self.phone.to_s # change to string in case input as all numbers
phone.gsub!(/[^0-9]/,"") # strip all non-digits
self.phone = phone # reset self.phone to new string
end
end
And here are the tests I've written, located in app_name/test/unit/client_test.rb
require 'test_helper'
class ClientTest < ActiveSupport::TestCase
# Test relationships
should have_many(:assignments)
should have_many(:deacons).through(:assignments)
should have_many(:interventions).through(:assignments)
# Test basic validations
should validate_presence_of(:last_name)
should validate_presence_of(:first_name)
should validate_presence_of(:gender)
should validate_presence_of(:address)
should validate_presence_of(:city)
should validate_presence_of(:state)
should validate_presence_of(:zip)
should validate_presence_of(:phone)
should validate_presence_of(:active)
# Identity-based tests
# tests for gender
should allow_value("male").for(:gender)
should allow_value("female").for(:gender)
should_not allow_value(nil).for(:gender)
should_not allow_value(1).for(:gender)
should_not allow_value("seahorse").for(:gender)
should_not allow_value("I believe gender is a societal construct.").for(:gender)
# tests for ethnicity
should allow_value("Asian").for(:ethnicity)
should allow_value("Black").for(:ethnicity)
should allow_value("Hispanic").for(:ethnicity)
should allow_value("Latino").for(:ethnicity)
should allow_value("Native American").for(:ethnicity)
should allow_value("White").for(:ethnicity)
should_not allow_value(nil).for(:ethnicity)
should_not allow_value(1).for(:ethnicity)
should_not allow_value(true).for(:ethnicity)
should_not allow_value(0.5).for(:ethnicity)
# tests for marital status
should allow_value("single").for(:marital_status)
should allow_value("married").for(:marital_status)
should allow_value("separated").for(:marital_status)
should allow_value("divorced").for(:marital_status)
should_not allow_value("White").for(:marital_status)
should_not allow_value(nil).for(:marital_status)
should_not allow_value(1).for(:marital_status)
should_not allow_value(true).for(:marital_status)
should_not allow_value("I believe marriage is a societal construct.").for(:marital_status)
# Contact-based Tests
# tests for address
should allow_value("123 Example Lane").for(:address)
should allow_value("456 Another Street").for(:address)
should_not allow_value(true).for(:address)
should_not allow_value(101).for(:address)
should_not allow_value(nil).for(:address)
# tests for zip
should allow_value("12345").for(:zip)
should_not allow_value("bad").for(:zip)
should_not allow_value("1234").for(:zip)
should_not allow_value("123456").for(:zip)
should_not allow_value("12345-6789").for(:zip)
# tests for state
should allow_value("OH").for(:state)
should allow_value("PA").for(:state)
should allow_value("WV").for(:state)
should_not allow_value("bad").for(:state)
should_not allow_value("NY").for(:state)
should_not allow_value(10).for(:state)
should_not allow_value("CA").for(:state)
# tests for phone
should allow_value("4122683259").for(:phone)
should allow_value("412-268-3259").for(:phone)
should allow_value("412.268.3259").for(:phone)
should allow_value("(412) 268-3259").for(:phone)
should_not allow_value("2683259").for(:phone)
should_not allow_value("14122683259").for(:phone)
should_not allow_value("4122683259x224").for(:phone)
should_not allow_value("800-EAT-FOOD").for(:phone)
should_not allow_value("412/268/3259").for(:phone)
should_not allow_value("412-2683-259").for(:phone)
# Assistance-based tests
# tests for gov_assistance
should allow_value(true).for(:gov_assistance)
should allow_value(false).for(:gov_assistance)
should_not allow_value(150).for(:gov_assistance)
should_not allow_value("Yes").for(:gov_assistance)
# tests for is_employed
should allow_value(true).for(:is_employed)
should allow_value(false).for(:is_employed)
should_not allow_value(30000).for(:is_employed)
should_not allow_value("Movie theater usher").for(:is_employed)
# tests for is_veteran
should allow_value(true).for(:is_veteran)
should allow_value(false).for(:is_veteran)
should_not allow_value(nil).for(:is_veteran)
should_not allow_value("Marines").for(:is_veteran)
# Establish context
# Testing other methods with a context
context "Creating seven clients" do
setup do
#dan = FactoryGirl.create(:client)
#barney = FactoryGirl.create(:client, :last_name => "Saha", :first_name => "Barney", :active => false, :ethnicity => "Indian" )
#ryan = FactoryGirl.create(:client, :last_name => "Black", :first_name => "Ryan", :phone => "412-867-5309", :ethnicity => "White", :gov_assistance => true )
#joe = FactoryGirl.create(:client, :last_name => "Oak", :first_name => "Joseph", :ethnicity => "Asian", :is_employed => false )
#mary = FactoryGirl.create(:client, :last_name => "Clute", :first_name => "Mary", :gender => "female", :ethnicity => "White" )
#jon = FactoryGirl.create(:client, :last_name => "Carreon", :first_name => "Jon", :is_veteran => true )
#meg = FactoryGirl.create(:client, :last_name => "Smith", :first_name => "Megan", :ethnicity => "White", :gender => "female", :is_employed => false)
end
# and provide a teardown method as well
teardown do
#dan.destroy
#barney.destroy
#ryan.destroy
#joe.destroy
#mary.destroy
#jon.destroy
#meg.destroy
end
# test one of each factory
should "show that all factories are properly created" do
assert_equal "Tabrizi", #dan.last_name
assert #ryan.active
assert #joe.active
assert_equal "Mary", #mary.first_name
assert #jon.active
assert #meg.active
deny #barney.active
end
# test the callback is working 'reformat_phone'
should "shows that Ryan's phone is stripped of non-digits" do
assert_equal "4128675309", #ryan.phone
end
# test the scope 'alphabetical'
should "shows that there are seven clients in in alphabetical order" do
assert_equal ["Black", "Carreon", "Clute", "Oak", "Saha", "Smith", "Tabrizi"], Client.alphabetical.map{|s| s.last_name}
end
# test the scope 'active'
should "shows that there are six active clients" do
assert_equal 2, Client.active.size
assert_equal ["Black", "Carreon", "Clute", "Oak", "Smith", "Tabrizi"], Client.active.alphabetical.map{|s| s.last_name}
end
# test the scope 'inactive'
should "shows that there is one inactive client" do
assert_equal 1, Client.inactive.size
assert_equal ["Saha"], Client.inactive.alphabetical.map{|s| s.last_name}
end
# test the scope 'receiving_gov_assistance'
should "shows that there is one client receiving government assistance" do
assert_equal 1, Client.receiving_gov_assistance.size
assert_equal ["Black"], Client.receiving_gov_assistance.alphabetical.map{|s| s.last_name}
end
# test the scope 'not_receiving_gov_assistance'
should "shows that there are six clients not receiving government assistance" do
assert_equal 6, Client.not_receiving_gov_assistance.size
assert_equal ["Carreon", "Clute", "Oak", "Saha", "Smith", "Tabrizi"], Client.not_receiving_gov_assistance.alphabetical.map{|s| s.last_name}
end
# test the scope 'male'
should "shows that there are five male clients" do
assert_equal 6, Client.male.size
assert_equal ["Black", "Carreon", "Oak", "Saha", "Tabrizi"], Client.male.alphabetical.map{|s| s.last_name}
end
# test the scope 'female'
should "shows that there are two female clients" do
assert_equal 2, Client.female.size
assert_equal ["Clute", "Smith"], Client.female.alphabetical.map{|s| s.last_name}
end
# test the scope 'employed'
should "shows that there are five employed clients" do
assert_equal 5, Client.employed.size
assert_equal ["Black", "Carreon", "Clute", "Saha", "Tabrizi"], Client.employed.alphabetical.map{|s| s.last_name}
end
# test the scope 'unemployed'
should "shows that there are two unemployed clients" do
assert_equal 2, Client.unemployed.size
assert_equal ["Oak", "Smith"], Client.unemployed.alphabetical.map{|s| s.last_name}
end
# test the scope 'veteran'
should "shows that there is one employed clients" do
assert_equal 1, Client.veteran.size
assert_equal ["Carreon"], Client.veteran.alphabetical.map{|s| s.last_name}
end
# test the method 'name' #DONE
should "shows name as last, first name" do
assert_equal "Tabrizi, Dan", #dan.name
end
# test the method 'proper_name' #DONE
should "shows proper name as first and last name" do
assert_equal "Dan Tabrizi", #dan.proper_name
end
end
end
Thank you very much for your help. If there are any other files that are necessary to determine the issue, please let me know. Sorry if this is very simple or too vague, I'm new to Rails development and I am using the terminology to the best of my knowledge
EDIT
Below is my client factory as it currently stands, in app_name/test/factories.rb
FactoryGirl.define do
factory :client do
last_name "Tabrizi"
first_name "Dan"
gender "male"
ethnicity "Hispanic"
marital_status "single"
address "123 Example Lane"
city "Anytown"
state "PA"
zip "12345"
phone { rand(10 ** 10).to_s.rjust(10,'0') }
gov_assistance false
is_employed true
is_veteran false
active true
end
end
Hope you perform tests against separate test database, so it is safe to do:
teardown do
Client.destroy_all
end
Like Simon suggested, try commenting out or removing your teardown. Usually in these situations teardown masks what the real error is, because it will compile all your code and try to initiate the destroy method which will fail because your factories will have returned nil. Since that happens it will return the last error that was documented, which is why your receiving that error.
I'm working through Hartl's book and I'm up to chapter 8. I've written some tests that I believe should be passing. I've quadruple checked my code against what's in the book, and double checked it against what's in the book's github repo, but I'm stumped. I'm getting the following errors from RSpec:
Failures:
1) UsersController POST 'create' should redirect to the user "show" page
Failure/Error: response.should redirect_to(user_path(assigns(:user)))
ActionController::RoutingError:
No route matches {:action=>"show", :controller=>"users", :id=>#<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, encrypted_password: nil, salt: nil>}
# ./spec/controllers/users_controller_spec.rb:93:in `block (3 levels) in <top (required)>'
2) UsersController POST 'create' should have a welcome message
Failure/Error: flash[:success].should =~ /welcome to the sample app/i
expected: /welcome to the sample app/i
got: nil (using =~)
# ./spec/controllers/users_controller_spec.rb:98:in `block (3 levels) in <top (required)>'
Finished in 0.83875 seconds
46 examples, 2 failures
Like I said, I've checked the code again and again. Restarted spork, restarted rails server, ran without spork. I've checked it against the code in the book and in the github repo. I've even copy/pasted the spec and controller code in the github repo, but all to no avail.
I'm stumped. It's late and I need to crash. :)
Hopefully one of you guys can see something I'm not. Here's what I've got so far...
users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
# ...
describe "POST 'create'" do
# ...
describe 'success' do
before(:each) do
#attr = { :name => 'New User', :email => 'some-email#gmail.com', :password => 'foobar', :password_confirmation => 'foobar' }
end
it 'should create a new user' do
lambda do
post :create, :user => #attr
end.should change(User, :count).by(1)
end
end
it 'should redirect to the user "show" page' do
post :create, :user => #attr
response.should redirect_to(user_path(assigns(:user)))
end
it 'should have a welcome message' do
post :create, :user => #attr
flash[:success].should =~ /welcome to the sample app/i
end
end
end
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#title = 'Sign up'
end
def show
#user = User.find params[:id]
#title = #user.name
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = 'Welcome to the Sample App!'
redirect_to #user
else
#title = 'Sign up'
render 'new'
end
end
end
user.rb
class User < ActiveRecord::Base
# Virtual properties (don't exist in db)
attr_accessor :password
# Accessible properties
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
# Return true if the user's password matches the submitted password
def has_password?(submitted_password)
# Compare encrypted_password with the encrypted version of submitted_password
encrypted_password == encrypt(submitted_password)
end
# Static/Class methods
def self.authenticate(email, submitted_password)
user = find_by_email email
return nil if user.nil?
return user if user.has_password? submitted_password
end
# Private functionality.
# Anything after the 'private' pragma will be inaccessable from outside the class
private
def encrypt_password
self.salt = make_salt if new_record? # Using ActiveRecord goodness to make sure this only gets created once.
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
routes.rb
SampleApp::Application.routes.draw do
#get '/users/new'
resources :users
match '/signup' => 'users#new'
match '/about' => 'pages#about'
match '/contact' => 'pages#contact'
match '/help' => 'pages#help'
root :to => 'pages#home'
end
Thanks in advance. If you guys really want to dig through or if I've missed something in my post, here's my code.
I'm very new to rails, and absolutely love it so far. Any help will be greatly appreciated.
Look closely at your users_controller_spec "success" spec: when will it create #attr? Before each test, or just before the "should create a new user" test? You use it in all the "POST 'create'" tests...
Once you make that non-spec-specific your tests will pass.
(By the way, having the code up in git is handy, but only if the code you're posting is actually checked in, otherwise... not as much ;)
your
it "should redirect to the user show page"
and
it "should have a welcome message"
is outside of the
describe "success" do
loop
How are you testing these lines in your ActionMailer specs?
default 'HEADER' => Proc.new { "information" },
:cc => "asdf#asdf.com",
:from => "asdf#asdf.com",
:to => "asdf#asdf.com"
Something like that:
# notifier_spec.rb
require "spec_helper"
describe Notifier do
describe "welcome" do
let(:user) { Factory :user }
let(:mail) { Notifier.welcome(user) }
it "renders the headers" do
mail.content_type.should eq('text/plain; charset=UTF-8')
mail.subject.should eq("Welcome")
mail.cc.should eq(["zombie#example.com"])
mail.from.should eq(["zombie#example.com"])
mail.to.should eq([user.email])
end
it "renders the body" do
mail.body.encoded.should match("Hi")
end
end
end
This is how Ryan Bates (= whole buncha people) does it.
i've been following the Rails tutorial (http://railstutorial.org/chapters/beginning , Rails 3 version), and i've stopped at 11th chapter when using Factory Girl and Rspec, I have a test that isn't passing and I feel I'm doing something wrong but I don't see what.
First of all there is a git repository on Github with the code that doesn't pass that test.
http://github.com/Monomachus/ch3_static_pages
So I got users model
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :microposts
.
.
.
I got microposts model
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
default_scope :order => 'microposts.created_at DESC'
end
Then I got Factory girl settings
Factory.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
Factory.define :micropost do |micropost|
micropost.content "Foo bar"
micropost.association :user
end
And finally Rspec code
require 'spec_helper'
describe Micropost do
.
.
describe "microposts associations" do
before(:each) do
#user = User.create(#attr)
#mp1 = Factory(:micropost, :user => #user, :created_at => 1.day.ago)
#mp2 = Factory(:micropost, :user => #user, :created_at => 1.hour.ago)
end
it "should have a microposts attribute" do
#user.should respond_to(:microposts)
end
it "should be in the reverse order of appearing" do
#user.microposts.should == [#mp2, #mp1]
end
end
end
And I got the error which definitely tells me that I do something wrong.
Failures:
1) Micropost microposts associations should be in the reverse order of appearing
Failure/Error: #user.microposts.should == [#mp2, #mp1]
expected: [#<Micropost id: 2, content: "Foo bar", user_id: nil, created_at: "2010-12-24 12:47:02", update
d_at: "2010-12-24 13:47:02">, #<Micropost id: 1, content: "Foo bar", user_id: nil, created_at: "2010-12-23 13:
47:02", updated_at: "2010-12-24 13:47:02">],
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[#<Micropost id: 2, content: "Foo bar", user_id: nil, created_at: "2010-12-24 12:47:02", updated_at: "20
10-12-24 13:47:02">,
- #<Micropost id: 1, content: "Foo bar", user_id: nil, created_at: "2010-12-23 13:47:02", updated_at: "20
10-12-24 13:47:02">]
+[]
# ./spec/models/micropost_spec.rb:42:in `block (3 levels) in <top (required)>'
As you can see even the user_id property is not set correctly +
apparently #user.microposts doesn't have any elements.
Please help me with this issue thanks.
Well the answer was simple :)
I included microposts associations in the Micropost spec.
And clearly
describe "microposts associations" do
before(:each) do
#user = User.create(#attr)
#mp1 = Factory(:micropost, :user => #user, :created_at => 1.day.ago)
#mp2 = Factory(:micropost, :user => #user, :created_at => 1.hour.ago)
end
it "should have a microposts attribute" do
#user.should respond_to(:microposts)
end
it "should be in the reverse order of appearing" do
#user.microposts.should == [#mp2, #mp1]
end
end
#attr did not contain the user properties but the micropost properties and of course #user = nil and then everything makes sense. So if you do have the same problem, include this code into User spec.
Now all my tests pass :)
By the time I had finished the pagination chapter, the tutorial was creating 100 sample users using Faker (listing 10.25 on page 390), and in RubyMine I was able to see my test was failing because the program was throwing an exception on duplicate user email address (which has a unique constraint). The #attr on line 8 of user_spec.rb has :email => "user#example.com", however this throws an exception since it's a duplicate email (I guess because Faker has already created it).
For me the fix was to copy #attr from line 8 and paste it into the describe "micropost associations" block (user_spec.rb), and change the email address to :email => "user#example999.com". I'm sure this is a total hack but I'm a n00b.
Update:
Another fix for me was to comment out the line #user = User.create(#attr), and simply create #mp1 and #mp2.
I was also getting test failure in this section, even though I already had "micropost associations" in user_spec.rb. Turns out I needed to restart spork and autotest in order to get them to use the new "micropost" factory in factories.rb.