Can I improve this controller action? - ruby-on-rails-3

Is this action ok? Can it be improved in any way? Should I extract the setting up of the order to a private method and just call that? Or is it fine and in line with the 'Rails way'?
def create
#order = Order.new(params[:order])
#product = Product.find(session[:product])
#order.amount = session[:total_amount]
#order.ip_address = request.remote_ip
#order.product_id = #product.id
#order.product_price = #product.price
#order.voucher = #voucher_value
#order.friend_id = session[:friend_id]
if #order.save
if #order.purchase
render :action => "success"
reset_friend_session_codes
else
render :action => "failure"
end
else
render :action => 'new'
end
end
TIA.

I would factor out as much as I could into a private method in the order model and call it with one of the model hooks, such as before_validation. That's too much logic for an action, and tramples all over MVC.

Related

check instance variable value in before filter rspec

In my controller I have a method which checks for next item. But before executing this method I need to set an instance variable which is written in the before_filter method. So how do I test it in rspec.
before_filter method check_items, :only[:next]
def check_items
if params[:item] == "Sherlock"
#item = $book1
elsif params[:item] == "Harry"
#queue = $book2
else
render :json=>{"Error" => "Book name does not exist"}
end
end
=======================
def next
#book = #item.pull
unless #book.nil?
respond_with(#book)
else
render :json =>{"msg"=>"Nothing to pull"}
end
end
If you are writing controller specs, you can test this behavior with assigns constraint. You may end up with something like this:
it "assigns #item" do
item = Item.create
get :next
expect(assigns(:item)).to eq(item)
end
If you write unit tests, you can use instance_variable_get method. But I would avoid it - I would go with controller specs.
Hope that helps

Rails render mail_form gem from a different controller

From travels, which belongs to offer, I'm trying to render a form generated with the mail_form gem.
But I get an error with this message:
*undefined method `model_name' for NilClass:Class*
travels/show
= render :partial => '/question_forms/form', :question_form => #question_form
Any hint how to get this? I've tried including the question_form model as an association to travel, but it doesn't work.
This is the question_forms_controller default code:
class QuestionFormsController < ApplicationController
def new
#question_form = QuestionForm.new
end
def create
begin
#question_form = QuestionForm.new(params[:question_form])
#question_form.request = request
if #question_form.deliver
flash.now[:notice] = 'Thank you for your message!'
else
render :new
end
rescue ScriptError
flash[:error] = 'Sorry, this message appears to be spam and was not delivered.'
end
end
end
In my travels_controller I tried to load #question_form using the same method, but this doesn't seem the way:
def new
#offer_season = OfferSeason.find(params[:offer_season_id])
#travel = #offer_season.travels.find(params[:id])
#question_form = #travel.questionforms.new
end
def create
#offer_season = OfferSeason.find(params[:offer_season_id])
#travel = #offer_season.travels.find(params[:id])
begin
#question_form = #travel.questionforms.new(params[:question_form])
#question_form.request = request
if #question_form.deliver
flash.now[:notice] = 'Thank you for your message!'
else
render :new
end
rescue ScriptError
flash[:error] = 'Sorry, this message appears to be spam and was not delivered.'
end
end

Refactor this controller?

class ArticlesController < ApplicationController
def index
#articles = Article.by_popularity
if params[:category] == 'popular'
#articles = #articles.by_popularity
end
if params[:category] == 'recent'
#articles = #articles.by_recent
end
if params[:category] == 'local'
index_by_local and return
end
if params[:genre]
index_by_genre and return
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #articles }
end
end
def index_by_local
# 10 lines of code here
render :template => 'articles/index_by_local'
end
def index_by_genre
# ANOTHER 10 lines of code here
render :template => 'articles/index_by_genre'
end
end
As you can see from above. My controller is not exactly thin. What its doing is, depending on the params that were passed, it interacts with the model to filter out records.
And if params[:local] or params[:genre] was passed. Then it calls its own methods respectively (def index_by_local and def index_by_genre) to do further processing. These methods also load their own template, instead of index.html.erb.
Is this quite typical for a controller to look? Or should I be refactoring this somehow?
We can move the first few lines into the model(article.rb):
def get_by_category(category)
# Return articles based on the category.
end
In this way we can completely test the article fetching logic using unit tests.
In general move all the code related to fetching records inside model.
Controllers in general
should authorize the user
get records using the params and assign them to instance variables
[These must typically be function
calls to model]
Render or redirect
I would define scopes for each of the collections you want to use.
class Article < ActiveRecord::Base
...
scope :popular, where("articles.popular = ?", true) # or whatever you need
scope :recent, where(...)
scope :by_genre, where(...)
scope :local, where(...)
...
def self.filtered(filter)
case filter
when 'popular'
Article.popular, 'articles/index'
when 'recent'
Article.recent, 'articles/index'
when 'genre'
Article.by_genre, 'articles/index_by_genre'
when 'local'
Article.local, 'articles/index_by_local'
else
raise "Unknown Filter"
end
end
end
Then in your controller action, something like this:
def index
#articles, template = Article.filtered(params[:category] || params[:genre])
respond_to do |format|
format.html { render :template => template }
format.xml { render :xml => #articles }
end
end

Basic Rails 3 saving parent object with association object

I have a basic rails question where I need to save two associated objects.
The association is Rtake has_many :companies and Company belongs_to :rtake
def create
#rtake = RTake.new(:email => params[:contact_email])
#rtake.role = "PROVIDER"
#company = #rtake.companies.build(params[:company])
#company.rtake = #rtake
respond_to do |format|
if #company.save_company_and_rtake
format.html{ redirect_to admin_companies_url}
else
flash.now[:errors] = #company.errors.full_messages.join(", ")
format.html{ render "new" }
end
end
end
In my company.rb class I have
def save_company_and_rtake
status1 = self.save(:validate => false)
status2 = self.rtake.save(:validate => false)
status = status1 && status2
status
end
The problem I face is that the company.rtake_id remains nil. Ideally shouldn't the company.rtake_id get updated to the #rtake.id after save.
I know I am missing something basic. Would appreciate some help.
You shouldn't need this line:
#company.rtake = #invitation
#invitation is nil from what you've shown .
But also, when you built the #company, #rtake.id isn't set because it hasn't been saved.
#company = #rtake.companies.build(params[:company])
#company.rtake = #rtake
#rtake.companies.build(params[:company]) This already means #company.rtake == #rtake. it's redundent here.

Populating form after validation fails

I have report_controller where I have two objects, #report and #reporte, and I want to save both objects in database. When validation fails, I want to get fields populated in rendered form.
I can't use just #report = ReportMain.new(params[:report_main]) because I have two objects and just one params object.
I use exportnew action to show form, and encreate to save this form.
There is some simple way to get form populated?
ReportController:
class ReportController < ApplicationController
before_filter :authenticate_user!
before_filter :load
layout "application"
def load
#company = Company.find_by_id(current_user.company_id)
#date = Date.today
#report = ReportMain.new
#reporte = ReportE.new
end
def index
list
render("list")
end
def list
#reports = ReportMain.all
end
def exportnew
render("ennew")
end
def encreate
#report = ReportMain.new
#reporte = ReportE.new
#reportparam = params[:report_main]
#report.waste_id = params[:waste][:code]
#report.warehouse_id = Warehouse.find_by_user_id(current_user.id).id
#report.user_id = current_user.id
#report.company_id = current_user.company_id
#report.amount = #reportparam[:amount]
#report.isimport = false
#report.isfinished = false
#report.reportnumber = ReportMain.where(:company_id => current_user.company_id).count.to_i+1
if #report.save
#reporte.report_main_id = #report.id
else
#report_main = #report
render("etnew")
return
end
#reporte.vrstaotpada = params[:vrstaotpada]
#reporte.nacinpakovanja = params[:nacinpakovanja]
#reporte.ispitivanjebroj = #reportparam[:ispitivanjebroj]
#reporte.datumispitivanja = #reportparam[:datumispitivanja]
#reporte.q_pripadnost = #reportparam[:q_pripadnost]
#reporte.datumpredaje = #date
if #reporte.save
redirect_to(:action => 'show', :id => #reporte.id)
flash[:notice] = "Izveštaj je uspešno kreiran."
else
#report_main = #report
render("etnew")
end
end
def show
#report = ReportMain.find(params[:id])
#warehouse = #report.warehouse.name
end
end
View starts with (it's huge HTML):
<%= form_for(:report_main, :url => {:action => 'encreate'}) do |f| %>
Since you want your form to get populated after validation fails, I suggest make your from to submit through AJAX. There is a jQuery plugin which can help http://jquery.malsup.com/form/