I have a game model that has some releases (has_many). I've chosen to use nested resources to express this.
resources :games do
resources :releases, only: [:new, :create, :destroy]
end
I'm trying to use RSpec to test my release controller actions. Here is a part of my test file.
describe "GET new" do
it "assigns a new release as #release" do
get :new, {}, valid_session
assigns(:release).should be_a_new(Release)
end
end
When I don't set game_id parameter I have a No Routes Match error. And when I add :game_id => 1 I have a RecordNotFound error.
So my question is how can I set a game object to express the nested resource ?
I'm really new to rspec too - be gentle :) Once you get into testing your apps properly, your life will be faster, better and less stressful. Your applications will benefit even more and so will your customers.
After a month or so, I don't use the browser until I need to style my pages. Faster and more efficient.
Personally, I started reading through this tutorial:
http://everydayrails.com/2012/04/07/testing-series-rspec-controllers.html
And also, as ever, the following RailsCasts helped enormously:
http://asciicasts.com/episodes/275-how-i-test
http://asciicasts.com/episodes/158-factories-not-fixtures
In your case, try something like this (without FactoryGirl).
describe "GET new" do
it "assigns a new release as #release" do
game = Game.create!( << INSERT VALID ATTRIBUTE >> )
get :new, {:game_id => game.id}, valid_session
assigns(:release).should be_a_new(Release)
end
end
I think that's right, haven't tested myself. There's a few other ways you could do it including put the game creation method is a before filter:
before(:each) do
#game = Game.create!( << INSERT VALID ATTRIBUTE >> )
end
Or, like I said, using Factory Girl to build the model for you.
This is how we do it.
Related
Have been spending some months trying to grok RSpec/TDD. Running into some challenges testing a controller with a nested attribute. I'm a bit fuzzy about how to properly set up and pass the parameters to the controller. I can build the controller so that it actually works as expected in the browser - I just can't seem to reverse engineer a test to confirm that.
Would appreciate any recommendations to a) fix the test below, and b) advise any better ways to do it (e.g. with mocks, stubs, etc).
Here's the basic model structure:
class School < ActiveRecord::Base
has_many :scholarships
end
class Scholarship < ActiveRecord::Base
belongs_to :school
end
I've configured the routes.rb as you'd expect, with:
resources :schools do
resources :scholarships, only: [:new, :create, :destroy]
end
In the controller, #new and #create are pretty standard for a Rails app:
class ScholarshipsController < ApplicationController
def new
#school = School.find(params[:school_id])
#scholarship = #school.scholarships.build
end
def create
#school = School.find(params[:school_id])
#scholarship = #school.scholarships.create(scholarship_params)
if #scholarship.save
flash[:success] = 'Scholarship created!'
redirect_to #school
else
render 'new'
end
end
private
def scholarship_params
params.require(:scholarship).permit(:name, ** Lots of params omitted for brevity **,
:notes, school: [:id])
end
end
The spec is where I can't seem to figure things out. For spec/controllers/scholarships_controller_spec.rb:
require 'rails_helper'
describe ScholarshipsController, type: :controller do
describe 'POST #create' do
context 'with valid attributes' do
before :each do
#school = create(:school)
#scholarship = #school.scholarships.create(FactoryGirl.build(:scholarship).attributes)
end
it 'receives :save' do
post :create, { scholarship: #scholarship.attributes, school: #school.id }
expect(#scholarship).to receive(:save)
end
end
end
end
When I run that test, I get the following error:
Failures:
1) ScholarshipsController POST #create with valid attributes receives :save
Failure/Error: post :create, scholarship: { attributes: #scholarship.attributes, school: #school.id } #school_id: #school.id, scholarship: #scholarship
ActionController::UrlGenerationError:
No route matches {:action=>"create", :controller=>"scholarships",
:scholarship=>{"id"=>"1", "name"=>"Dynamic Metrics Director Scholarship", *** Lots of parameters omitted for brevity ***
, "school_id"=>"2"}, :school=>"2"}
The parameters look correct to me. there's a set of attributes for the scholarship, and for the school. But the routing isn't working. I've tried a dozen different ways to try and get this to work. Heartened that I'm apparently passing a (more or less correct) parameters hash, but can't figure out quite where I'm going wrong.
****** EDIT ******
Updated in response to an answer posted below.
Changed the syntax of the spec as suggested by Srdjan:
it 'receives :save' do
post :create, "schools/#{#school.id}/scholarships", { scholarship: #scholarship.attributes, school_id: #school.id }
expect(#scholarship).to receive(:save)
end
This changes the error message. I assume that indicates that the parameters are being passed correctly, since it's no longer throwing an error related to routes/params..? Error message is:
1) ScholarshipsController POST #create with valid attributes receives :save
Failure/Error: expect(#scholarship).to receive(:save)
(#<Scholarship:0x007fe293b02598>).save(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Just for good measure, here are the relevant routes, which I hadn't posted previously:
school_scholarships POST /schools/:school_id/scholarships(.:format) scholarships#create
new_school_scholarship GET /schools/:school_id/scholarships/new(.:format) scholarships#new
school_scholarship DELETE /schools/:school_id/scholarships/:id(.:format) scholarships#destroy
In your test, you're POST-ing to the wrong route. As setup in routes.rb, scholarship resources do not exist out of the context of a school resource.
In order to fix this, you have to answer a question: "Does it make sense for a user to access a scholarship record without having to specify a school?"
If the answer is yes, you can either copy the scholarships route and paste them outside of the schools resource block. This way, you can have access to scholarships without having to specify a school, but also with specifying a school.
If the answer to the question is no, then you need to fix your test as such:
it 'receives :save' do
post :create, "schools/#{#school.id}/scholarhips", { scholarship: #scholarship.attributes, school_id: #school.id }
expect(#scholarship).to receive(:save)
end
I am a Rails newbie and am struggling with appears to be a routing bug.
I have a Site object that holds information about a website and how to crawl it. I want to test my code's connection to the site. Clicking "test site" in my app creates an error:
Couldn't find Site without an ID
app/controllers/sites_controller.rb:86:in `test_site'
routes.rb:
...
post "/test_site" => "sites#test_site"
get "home/index"
resources :sites, :logins
...
index.html.erb
...
<% #sites.each do |site| %>
<tr>
<td>
<%= form_tag test_site_path(site) do -%>
<div><%= submit_tag 'Test site' %></div>
<% end -%>
...
sites_controller.rb
...
def test_site
#site = Site.find(params[:id])
...
It looks like the Sites controller is not getting a Site :id from test_site_path(site). I'm not sure how to setup routes and pass the ID correctly.
Thanks!
Edit: I tried adding this code to my routes.rb:
resources :sites do
get "/test_site", :action => "test_site", :on => :member
end
I get this error:
No route matches {:controller=>"sites", :action=>"test_site", :format=>#<Site id: 11, ...
What might I be doing wrong?
For a newbie the best thing I can tell you to do is to get in the process of writing a controller test when you are adding a new feature as it helps debug issue like this. Use restful actions instead of creating your own. Try to use index, new, edit, create, update, show, destroy instead.
sites_controller_spec.rb
require 'spec_helper'
describe SitesController do
let(:site) { Site.create() } #Add to create what a site requires, eventually this will become a fixture but don't worry about that now
describe "#show" do
before { get :show, id: site.to_param }
it "responds with success" do
response.should be_success
end
end
end
Run this test. It will tell you that you have no route
routes.rb
resources :sites, only: %w[show]
Run this test. It will fail and tell you to create a sites controller and a show action:
class SitesController < ApplicationController
def show
end
end
Run the test again. It will now complain because there is no view.
View -> create directory (sites) -> inside create a file show.html.erb
Run the test again. It now is passing. Your routing is now working correctly, using REST, and checking that it is working is now automated.
This may seem a bit imitating right now, but I guarantee that if you make this a habit it will become second nature and you won't have to deal with routing bugs again.
I'm writing controller tests in Rails and RSpec, and it seems from reading the source code of ActionController::TestCase that it's not possible to pass arbitrary query parameters to the controller -- only routing parameters.
To work around this limitation, I am currently using with_routing:
with_routing do |routes|
# this nonsense is necessary because
# Rails controller testing does not
# pass on query params, only routing params
routes.draw do
get '/users/confirmation/:confirmation_token' => 'user_confirmations#show'
root :to => 'root#index'
end
get :show, 'confirmation_token' => CONFIRMATION_TOKEN
end
As you may be able to guess, I am testing a custom Confirmations controller for Devise. This means I am jacking into an existing API and do not have the option to change how the real mapping in config/routes.rb is done.
Is there a neater way to do this? A supported way for get to pass query parameters?
EDIT: There is something else going on. I created a minimal example in https://github.com/clacke/so_13866283 :
spec/controllers/receive_query_param_controller_spec.rb
describe ReceiveQueryParamController do
describe '#please' do
it 'receives query param, sets #my_param' do
get :please, :my_param => 'test_value'
assigns(:my_param).should eq 'test_value'
end
end
end
app/controllers/receive_query_param_controller.rb
class ReceiveQueryParamController < ApplicationController
def please
#my_param = params[:my_param]
end
end
config/routes.rb
So13866283::Application.routes.draw do
get '/receive_query_param/please' => 'receive_query_param#please'
end
This test passes, so I suppose it is Devise that does something funky with the routing.
EDIT:
Pinned down where in Devise routes are defined, and updated my example app to match it.
So13866283::Application.routes.draw do
resource :receive_query_param, :only => [:show],
:controller => "receive_query_param"
end
... and spec and controller updated accordingly to use #show. The test still passes, i.e. params[:my_param] is populated by get :show, :my_param => 'blah'. So, still a mystery why this does not happen in my real app.
Controller tests don't route. You are unit-testing the controller--routing is outside its scope.
A typical controller spec example tests an action:
describe MyController do
it "is successful" do
get :index
response.status.should == 200
end
end
You set up the test context by passing parameters to get, e.g.:
get :show, :id => 1
You can pass query parameters in that hash.
If you do want to test routing, you can write routing specs, or request (integration) specs.
Are you sure there isn't something else going on? I have a Rails 3.0.x project and am passing parameters.. well.. this is a post.. maybe it's different for get, but that seems odd..
before { post :contact_us, :contact_us => {:email => 'joe#example.com',
:category => 'Category', :subject => 'Subject', :message => 'Message'} }
The above is definitely being used in my controller in the params object.
I am doing this now:
#request.env['QUERY_STRING'] = "confirmation_token=" # otherwise it's ignored
get :show, :confirmation_token => CONFIRMATION_TOKEN
... but it looks hacky.
If someone could show me a neat and official way to do this, I would be delighted. Judging from what I've seen in the source code of #get and everything it calls, there doesn't seem to be any other way, but I'm hoping I overlooked something.
I am developing a app in ruby on rails for a local business. The pages are 'static', but changeable through a backend CMS I am building for them. Is there a best practice to creating a controller for static pages? Right now I have a sites controller with all static routes, like this.
routes.rb
get "site/home"
get "site/about_us"
get "site/faq"
get "site/discounts"
get "site/services"
get "site/contact_us"
get "site/admin"
get "site/posts"
or would I be better off creating member routes for the site controller like this without the crud, because a 'Site' will not need to have the CRUD.
resources :sites, :except => [:index, :new, :create, :update, :destroy]
member do
get :home
get :about_us
get :faq
get :discounts
get :services
get :contact_us
get :admin
get :posts
end
Or is there a best practice / better way? Any answers would be appreciated. Thanks
If the static pages list are not going to increase, then you can keep the list, but if you want a dynamic list like site/any_new_url , save the routes as
get 'site/:cms_page' => 'cms#show' # all requests matching site/any_page will go CmsController, show method
This will help reduce keep the routes from bloating, but the downside is you do not know what all routes are the valid ones. Your sample code can be
def show
#page_data = Page.find_by_page(:params[:cms_page])
end
show.html.erb
<%= #page_data.html_safe %>
Dunno yet if I consider this a best practice or an abomination but here is what I came up with when tackling the same problem.
My reasoning is that the site was providing some specified functionality (which doesn't really matter for this discussion) + a bunch of information about the organisation itself (about us, contact, FAQ, homepage blurb, whatever). Since all that data was really related to the organisation, an Organisation model seemed reasonable with each of those things as attributes. Here is the model:
class Organisation < ActiveRecord::Base
...validations stuff...
def self.attrs_regex
Regexp.new(self.attrs.join("|"))
end
def self.attrs
self.column_names.reject{|name| name =~ /id|created_at|updated_at/}
end
end
Then I use the attrs class method to generate routes based on the columns. This is in my routes.rb:
Organisation.attrs.each do |attr|
get "#{attr}" => "organisation##{attr}", :as => attr.to_sym
get "#{attr}/edit" => "organisation#edit", :as => "#{attr}_edit".to_sym, :defaults => { :attribute => attr }
post "#{attr}" => "organisation#update", :as => :organisation_update, :defaults => { :attribute => attr}, :constraints => Organisation.attrs_regex
end
The controller gets a little weird and I am not thrilled with the code here but here it is anyway. I need to make sure the attribute is set and available to the views so I can do the right thing there so I set it in the application controller:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_attribute
def set_attribute
#attribute = action_name.parameterize
end
end
For the organisation controller I just set the #organisation variable to be the first and only row in the database in the before_filter and then let Rails do its usual magic of calling the method, failing, and rendering a view of the same name. The edit action just uses one view file to edit all the different attributes:
class OrganisationController < ApplicationController
before_filter :set_organisation
def edit
authorize! :edit, #organisation
#attribute = params[:attribute].parameterize
end
def update
authorize! :update, #organisation
#attribute = params[:attribute]
respond_to do |format|
if #organisation.update_attributes(params[:organisation])
format.html do
redirect_to "/#{#attribute}", notice: t('successful_update')
end
format.json { head :ok }
else
format.html { render action: "edit" }
end
end
end
private
def set_organisation
#organisation = Organisation.first
end
end
So that is where I ended up. Like you I hit up SO to tap into the seething mass of genius here but ended up with disappointing results. If there is something better out there I am still hoping to find it.
What I like about what I did is that routes are automatically generated based on the structure of the organisation table.
What I don't like about what I did is that routes automatically generated based on the structure of the organisation table.
I know I will pay for that design decision when I have to deal with i18n routing and there are probably a thousand other reasons that this is a bad idea that I have yet to discover but for the moment I have a happy client.
In the end this is not a suggestion that you should do this, but I am hoping to give you more than I got so you can advance your thinking on this and hopefully end up a little closer to that best practice.
If you are going to construct a CMS, which likely connects to a database, and allow your customer to change the text on the pages of their site, I would not recommend using static pages. In Rails terms, a static page would refer to creating html files in your /views/pages directory. If you go this route, then you're walking outside of the way that Rails was designed.
I believe that what you want to do is create tables in the database that correspond to and store the data for your posts, etc. You can pull information into the controller from the model that it corresponds to and then user a view to display the data. You can create a layout for these pages and then create controllers for each of the pages that you add.
As far as routes, I would recommend using the following:
map.resource :controller_name
you then would add the code that displays the information from the CMS in the corresponding show controller action and view for each page.
I'm using Rails 3.0 and I have the following in my routes.rb files:
scope "/:location" do
resources :agents
end
So I can do this:
agents = Agent.where("location = ?", params[:location])
(There might be a better way to do this..? Mostly I want URLs like: myurl.com/london/agents)
Anyway, my problem is with the tests (which I'm still learning), how can I make this work with the scope:
class AgentsControllerTest < ActionController::TestCase
test "should get index" do
get 'index'
assert_response :success
end
end
It just gets a route no found error.
Any help would be great, thanks.
Because you have scoped your resources, you don't actually have direct access to the agents_controller via any actions. So calling get 'index' is really just trying to access /agents. If you tried this in your browser, it would fail as well.
To get a success you'll have to supply a location param like so:
get 'index', { :location => 'hawaii' }
Hope that helps.
Edit: If you want additional access to the agents_controller (meaning location is optional) simply add a top-level resource for it in your routes file:
resources :agents