Following the Railscasts am trying to set up a scheduled rake task to send an email to an administrator user:
desc 'Testing rake task to generate email'
task :overtime_report => :environment do
hospital_bookings = HospitalBooking.scoped
hospital_booking = hospital_bookings
user = User.where(:roles => :administrator)
if params[:format] == 'pdf'
hospital_bookings = hospital_bookings.where(:day => Date.today.beginning_of_month..Date.today.end_of_month)
end
respond_to do |format|
format.html
format.pdf do
render :pdf => "#{Date.today.strftime('%B')} Overtime Report",
:header => {:html => {:template => 'layouts/pdf.html.erb'}}
OvertimeMailer.overtime_pdf(user, hospital_booking).deliver
end
end
end
Controller
class HospitalBookingsController < ApplicationController
#load_and_authorize_resource
before_filter :admin_user, :only => [:index]
def index
#hospital_bookings = HospitalBooking.scoped
system "rake overtime_report Mail_ID=#{params[:id]} &"
flash[:notice] = 'Delivering Overtime Report'
end
Just need a bit of guidance if I am heading in the right direction.
Not much of your code makes sense I'm afraid:
A rake task is not an action in a controller so your use of params and respond_to will not work at all.
If it's a scheduled task to be run at a certain interval then you don't want a controller at all.
Your controller contains a huge security vulnerability by passing user input directly to the command line in the form of Mail_ID=#{params[:id]}.
Passing the Mail_ID option above into the rake task doesn't make sense as it's never used.
You should never call system commands from a controller.
hospital_booking = hospital_bookings makes no sense as it does nothing.
render isn't going to do anything either, that's the responsibility of the ActionMailer object you create.
You probably want something like:
desc 'Send hospital bookings overtime report'
task :overtime_report => :environment do
bookings = HospitalBooking.where(:day => Date.today.beginning_of_month..Date.today.end_of_month).all
user = User.where(:roles => :administrator).first
OvertimeMailer.overtime_pdf(user, hospital_bookings).deliver
end
Related
I'm in Rails 3. Here's my code for a method which creates Update records in response to certain attributes being changed on a model called Candidate:
before_save :check_changed, on: [:update]
def check_changed
tracked_attributes = ["period_contributions", "total_contributions",
"period_expenditures", "total_expenditures",
"debts_and_loans", "cash_on_hand",
"major_endorsements",
"rating_afl_cio",
"rating_cal_tax",
"rating_cc",
"rating_eqca",
"rating_lcv",
"rating_now"]
changes.each do |key, value|
if tracked_attributes.include?(key)
Update.create(:candidate_id => self.id, :attribute_name => key,
:new_value => value[1], :old_value => value[0])
end
end
end
The issue is that I have some rake tasks I'm running to do batch updates to the data, which end up triggering this callback unintentionally. I'd like for it only to run when a Candidate is updated from within the admin tool aka CRUD interface. Any advice on the best way to do this?
I will only use callbacks when it is something that always needs to happen, no matter the source. Magically skipping or including them normally leads to pain down the road.
My suggestion is to create a different method on the model that does the check and use that for the crud actions.
class Candidate
#...
def check_changed_and_update(attributes)
check_changed
update(attributes)
end
private
def check_changed
tracked_attributes = ["period_contributions", "total_contributions",
"period_expenditures", "total_expenditures",
"debts_and_loans", "cash_on_hand",
"major_endorsements",
"rating_afl_cio",
"rating_cal_tax",
"rating_cc",
"rating_eqca",
"rating_lcv",
"rating_now"]
changes.each do |key, value|
if tracked_attributes.include?(key)
Update.create(:candidate_id => self.id, :attribute_name => key,
:new_value => value[1], :old_value => value[0])
end
end
end
end
Then in the controller for candidate just change update to check_changed_and_update:
class CanidateController < ApplicationController
def update
#...
respond_to do |format|
if #candidate.check_changed_and_update(canidate_params)
format.html { redirect_to #candidate, notice: 'Candidate was successfully updated.' }
else
format.html { render action: 'edit' }
end
end
end
This has an added bonus of making it obvious what is going to happen when update is called.
Now you can just use the normal active record api in your rake tasks.
What I'm thinking right now is...
I have a library full of books (entries). Each book has many checkouts (embedded document).
What I think I want to do is, upon checkout, make a new "checkout" as an embedded document. Upon checkin, I want to edit the checkout and add a "date_checked_out" field...
The issue is, my current model/controller makes a new entry each time there is a checkin or checkout...so it's doubly redundant...
What's the best way to go about this? Need more detail?
Checkout Controller:
def new
#entry = Entry.find(params[:entry_id])
#checkout = #entry.checkout.new
respond_to do |format|
format.html {render :layout => false}
end
end
def create
#entry = Entry.find(params[:entry_id])
#entry.update_attributes(:checked_out => "Out")
#checkout = #entry.checkout.create!(params[:checkout])
redirect_to "/", :notice => "Book Checked Out!"
end
class Checkout
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :checkout_date, :type => Time
field :checkout_date_due, :type => Time
field :book_in, :type => Time, :default => Time.now
field :book_out, :type => Time, :default => Time.now
embedded_in :entries, :inverse_of => :entries
end
It makes sense the checkout would have a start and stop date. Do you need to make a checkout when a checkin occurs? You may be able to change this to an 'update' instead of a 'create' on the checkout controller - enter a checked_in_at on update.
Specifically - you'd want to be able to accept a PUT on the checkout controller - this could either be generic (allowing you to update the checkout in many ways) or specific, make a route that cleans up this for you:
resources :checkouts do
put :checkin, :on => :member
end
in checkouts_controller
def checkin
#checkout = Checkout.find(params[:id]
#checkout.update_attribute(:checked_in_at, Time.now)
# handle issues, redirect, etc.
end
Keeping it pure REST, add an update action to your Checkout controller.
Also, post your entry model. I'm assuming from your code that an entry has_one checkout, and a checkout belongs to an entry.
Something like:
*Edit because it appears OP wants to see how this works while checking for a conditional
... original boilerplate code ommitted
def update
#entry = Entry.find(params[:entry_id])
# if the book is checked out
if #entry.checked_out == "out"
# then update it
#entry.update_attributes(:checked_out => "whatever" # though I'd seriously consider changing checked out to a boolean - if it's either out or in, true or false makes sense. Ignore this advice if there are more than two states
#checkout = #entry.checkout
respond_to do |format|
if #checkout.update_attributes(:checked_out => "newValue")
...
else
.. handle errors
end
end
else
#the book does not have the correct status
respond_to do |format|
format.html { redirect_to some_action_path, :notice => "Entry is not out, so we cannot update its status." }
format.json { render json: #entry.errors, status: :unprocessible_entry }
end
end
end
Also, if you want to make the code a bit more explicit, you might consider taking swards advice and creating a few named endpoints like
def checkout
end
def checkin
end
I think that makes sense, in that someone else reading the code can very easily know exactly what that controller action is doing, as opposed to create and update.
This method works OK, but if I add delayed_job's handle_asychronously, I get can't convert nil into String:
def onixtwo
s = render_to_string(:template=>"isbns/onix.xml.builder")
send_data(s, :type=>"text/xml",:filename => "onix2.1.xml")
end
handle_asynchronously :onixtwo
So rendering with delayed job is clearly having a problem with params being passed. I've tried putting this job in a rake task but render_to_string is a controller action - and I'm using a current_user variable which needs to be referenced in the controller or view only. So... what's the best way to delay a rendering job?
/////////update////////
Given that I'm currently pair-programming with a toddler, I don't have the free hands to investigate additional class methods as wisely recommended in the comments - so as a quick and dirty I tried this:
def onixtwo
system " s = render_to_string(:template=>'isbns/onix.xml.builder') ; send_data(s, :type=>'text/xml',:filename => 'onix2.1.xml') & "
redirect_to isbns_path, :target => "_blank", :flash => { :success => "ONIX message being generated in the background." }
end
Why doesn't it work? No error message just no file produced - which is the case when I remove system ... &
For what it's worth, this is what I did, bypassing render_to_stream entirely. This is in /lib or app/classes (adding config.autoload_paths += %W(#{config.root}/classes into config/application.rb):
#classes/bookreport.rb
# -*- encoding : utf-8 -*-
require 'delayed_job'
require 'delayed/tasks'
class Bookreport
# This method takes args from the book report controller. First it sets the current period. Then it sorts out which report wants calling. Then it sends on the arguments to the correct class.
def initialize(method_name, client, user, books)
current_period = Period.where(["currentperiod = ? and client_id = ?", true, client]).first
get_class = method_name.capitalize.constantize.new
get_class.send(method_name.to_sym, client, user, books.to_a, current_period.enddate)
end
end
#app/classes/onixtwo.rb
require 'builder'
class Onixtwo
def onixtwo(client_id, user_id, books, enddate)
report_name = "#{Client.find_by_id(client_id).client_name}-#{Time.now}-onix-21"
filename = "#{Rails.root}/public/#{report_name}.xml"
current_company = Company.where(:client_id => client_id).first
File.open(filename, "w") do |file|
xml = ::Builder::XmlMarkup.new(:target => file, :indent => 2)
xml.instruct!(:xml, :version => "1.0", :encoding => "utf-8")
xml.declare! :DOCTYPE, :ONIXMessage, :SYSTEM, "http://www.editeur.org/onix/2.1/03/reference/onix-international.dtd"
xml.ONIXMessage do
xml.Header do
#masses of Builder code goes here...
end
end #of file
xmlfile = File.open(filename, "r")
onx = Onixarchive.new(:client_id => client_id, :user_id => user_id)
onx.xml = xmlfile
onx.save!
end #of method
handle_asynchronously :onixtwo
end #of class
Called from the view like this:
= link_to("Export to ONIX 2.1", params.merge({:controller=>"bookreports" , :action=>:new, :method_name => "onixtwo"}))
Via a controller like this:
class Books::BookreportsController < ApplicationController
#uses Ransack for search, hence the #q variable
def new
#q = Book.search(params[:q])
#books = #q.result.order('pub_date DESC')
method_name = params[:method_name]
Bookreport.new(method_name, #client, #user, #books)
redirect_to books_path, :flash => {:success => t("#{method_name.to_sym}").html_safe}
end
end
I'm really struggling trying to learn rspec :( So I hope you can give me a little bit of help with a really simple create-action in the controller. I would like to use Rspec::mocks for this, as I think that is the way to do it? Instead of having to hit the database when testing.
I'm having a before_filter:
def find_project
#project= Project.find_by_id(params[:project_id])
end
The create action looks like this:
def create
#batch = Batch.new(params[:batch])
#batch.project = #project
if params[:tasks]
params[:tasks][:task_ids].each do |task_id|
#batch.tasks << Task.find(task_id)
end
end
if #batch.save
flash[:notice] = "Batch created successfully"
redirect_to project_batch_url(#project, #batch)
else
render :new
end
end
I'm really in doubt when it comes to #batch.project = #project how do I define #project? And also the whole params[:tasks][:task_ids].each part.. Ya.. pretty much the whole thing :(
Sorry for this newbie question - Hope you guys can help or atleast point me in the right direction :)
Thanks
The idea of a controller spec is to check whether the actions are setting instance variables, and redirecting/rendering as needed. To set up the spec, you would normally create an object or a mock, set attributes/stubs, and then call the action, passing a params hash if necessary.
So for example (air code):
describe MyController do
before(:each) do
#project = mock_model(Project)
Project.stub(:find_by_id) {#project}
#batch = mock_model(Batch)
Batch.stub(:new) {#batch}
end
it "should redirect to project_batch_url on success" do
#batch.stub(:save) {true)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should redirect_to(project_batch_url(#project,#batch))
end
it "should render :new on failure" do
#batch.stub(:save) {false)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should render_template("new")
end
end
You can find lots more information about this in the RSpec Rails docs.
Using BDD helps you define your interfaces. So if your controller wants the project to create a batch and add some task id's, then "write the code you wish you had." In practice for controllers, this means trying to push logic out of the controller and into your models. Testing models tends to be more intuitive and are definitely faster than testing controllers.
Here are some possible specs (untested) from the "mockist" point of view:
# controller spec
describe BatchesController do
def mock_project(stubs={})
#mock_project ||= mock_model(Project, stubs)
end
def mock_batch(stubs={})
#mock_batch ||= mock_model(Batch, stubs)
end
context "POST create"
it "calls #create_batch_and_add_tasks on the project"
mock_project.should_receive(:create_batch_and_add_tasks).with(
:batch => { :name => 'FooBatch' },
:task_ids => [1,2,3,4]
)
Project.stub(:find).and_return(mock_project)
post :create, :batch => { :name => 'FooBatch' }, :tasks => { :task_ids => [1,2,3,4] }
# consider changing your params to :batch => { :name => 'FooBatch', :task_ids => [1,2,3,4] }
end
it "redirects to the project_batch_url on success" do
mock_project(:create_batch_and_add_tasks => mock_batch(:save => true))
Project.stub(:find) { mock_project }
post :create, :these_params => "don't matter because you've stubbed out the methods"
end
# controller
def create
#batch = #project.create_batch_and_add_tasks(
:batch => params[:batch],
:task_ids => params[:tasks].try([:tasks_ids])
)
if #batch.save
...
Below I listed some code from simple Rails application. The test listed below fails in last line, because the updated_at field of the post is not changed within the update action of PostController in this test. Why?
This behaviour seems to me a little strange, because standard timestamps are included in Post model, live testing on local server shows that this field is actually updated after returning from update action and first assertion is fulfilled thus it shows the update action went ok.
How can I make fixtures updateable in above meaning?
# app/controllers/post_controller.rb
def update
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post # Update went ok!
else
render :action => "edit"
end
end
# test/functional/post_controller_test.rb
test "should update post" do
before = Time.now
put :update, :id => posts(:one).id, :post => { :content => "anothercontent" }
after = Time.now
assert_redirected_to post_path(posts(:one).id) # ok
assert posts(:one).updated_at.between?(before, after), "Not updated!?" # failed
end
# test/fixtures/posts.yml
one:
content: First post
posts(:one)
That means "fetch the fixture named ":one" in posts.yml. That's never going to change during a test, barring some extremely weird and destructive code that has no place in sane tests.
What you want to do is check the object that the controller is assigning.
post = assigns(:post)
assert post.updated_at.between?(before, after)
On a side note if you were using shoulda (http://www.thoughtbot.com/projects/shoulda/) it would look like this:
context "on PUT to :update" do
setup do
#start_time = Time.now
#post = posts(:one)
put :update, :id => #post.id, :post => { :content => "anothercontent" }
end
should_assign_to :post
should "update the time" do
#post.updated_at.between?(#start_time, Time.now)
end
end
Shoulda is awesome.