Rails 3 Nested Resource Variables - ruby-on-rails-3

I have a few questions relating to a Transaction object that I'm creating.
Transaction belongs_to Loan and Loan has_many Transactions.
Therefore, I setup a nested route:
resources :loans do
resources :transactions
end
My question is: How do I pass the loan value into the Transaction's 'loan_id' field? Is this best done in the controller or as a hidden_field in the form? Does the nested route create an easy way to grab this variable?
I assumed this would be done automatically, but the field is empty when I saved it as-is.
Any help would be greatly appreciated!

if you call a specific transaction, the route for a new transaction will be
loans/:loan_id/transactions/new
you could use model association like this: in your create action:
#transaction = Loan.find(params[:loan_id]).transactions.build
that way your new #transaction will be already populated with the loan_id

Consider adding a before_filter to your controller and having it call a private method to grab the :id across all actions. Place this at the top of your transactions controller:
before_filter :load_loan
And then after all the actions, add:
private
def load_loan
#loan.find(params[:loan_id])
end
Use it like this in your new action:
#transaction = #loan.transactions.build

Related

Rails: create lots of almost-duplicate records

I want to write a method that creates a bunch of almost-duplicate records, just with one or two parameters changed. I'll make a form to control those parameters, I'm just wondering about how best to write the method, and where do keep it.
Presently in my document.rb I've written this:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create(:component_ids => components, :message => 'Message.', :template_id => template.id, :user_id => user.id)
end
end
It doesn't feel right though. Is there a better way to do this?
This code is fine if your security model allows all these fields to be bulk assignable by mention in attr_accessible in the model. If it doesn't then you're better off using the block form of create. Also, if Document, Template and User are ActiveRecord instances, you should let Rails manage the details of ids.
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.component_ids = components,
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
One final note is that component_ids must be serialized to store a list. This is probably a flaw in your model design. The better way is (probably) to specify Component belongs_to User and also User has_many Components. I.e. Component contains a foreign key to User. If it's necessary for a Component to belong also to many users, then you'll need either has_and_belongs_to_many or has_many ... through. The Rails guide on relations describes all this in more detail.
With the right relations set up, the code will become:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.components = components, # Components is now a list of active records.
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
The resulting SQL will get all the foreign keys and (if necessary) relation tables filled in correctly.

Wicked Rails Gem Help Wiring Up

I want to do a multi-step form for taking in new information. One page I want to collect name/contact info, the next page I want to collect medical history, the third page demographic information.
I've installed the Wizard gem and generated a dedicated controller. All of the tutorials I've seen on it apply to devise and the signup process so I'm a little bit lost on the controller actions and the instance variables and how I should be writing them.
Was wondering if anyone has a tutorial other than a sign-up one that could maybe help me along in learning how to get this all wired up.
Any pointers or assistance is appreciated.
EDIT:
I think my problem is in the controller for my wizard.
In the show and update actions the demo shows to declare the variable of
#user = current_user
That's great, but it's a helper method that I don't need. I need to create a patient, store the patient_id in a session which I do in my create action in my main patients controller. Then somehow pass that over to the patientsteps controller.
Here's what I've tried in patientsteps
class PatientstepsController < Wicked::WizardController
before_filter :authenticate_user!
steps :medical, :summary
def show
#patient = Patient.find(params[:patient_id])
render_wizard
end
def update
#patient = Patient.find(params[:id])
#patient.attributes = params[:patient]
render_wizard #patient
end
end
When I do this, I get cannot find a patient without and ID. I understand that I'm doing this wrong, but I'm not sure how to pass in the patient_id that was created in my patients controller create action.
Patients Controller Create:
def create
#patient = Patient.new(params[:patient])
if #patient.save
session[:patient_id] = #patient.id
redirect_to patientsteps_path, notice: "Patient was successfully created."
else
render :new
end
end
In your show action, instead of params[:patient_id] you should use session[:patient_id], because the id of the patient is stored in the session, not in the params hash.
Then in the update action, you will receive the patient id in params[:patient_id], not [:id], because wicked uses params[:id] to identify which step the wizard is on.

Rails best practices - where should this code go?

There are a couple of places where I could do what I need, but I'm not sure where the best place is in line with good practices.
I have an Orders controller, and after a successful order is created I want to create a subscription (but only if the order is a success), and a referral (but only if the order is associated with one).
Now the obvious choice is to use after_create on the Order model... but... how can I get session data into that? (The referral ids, friend ids and voucher ids are only in the session as there's no need to store them in the Order db).
So should I just create the Subscription and Referral objects in the create action (how I have it at the mo) or is there a better way?
Here's my create action:
(#order.purchase only returns true if the payment was successful)
def create
if #order.save
if #order.purchase
Subscription.create(:order_id => #order.id, :product_id => #order.product_id)
if #order.voucher
Referral.create(:user_id => session[:friend_id], :order_id => #order.id,
:voucher_amount => #voucher_value)
end
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
Any help would be appreciated - I really would like to do this properly so I hope no one minds me asking what is probably a simple question.
I recently had a similar question, please take a look, i think that a simple virtual attribute in a callback will do it for you as well.
Fetch current user in after_create filter
use of callbacks will make your life easy, you need to use after_save
do all your stuff in after_save callback of order model. see rails api doc for callback here
Edit: If the session variable is not available with model, you can have a post_save method to deal with all logic which also accepts all require params like
like
class Order < ActiveRecord::Base
def post_save require_attr
#create subscriptions
# create referral
end
end

Rails 3 subsequent forms submission (second dependent on first)

I am trying to achieve a subsequent form submission. To clarify things -
I submit a form for #post
then once that #post is created I would immediately (under the hood) like to submit the form for #associations.
The catch is, this second form submission would require the post_id field from the newly created #post.
What would be the best way to achieve this? Would nested forms help me pull the newly created #post.id? Kindly help me with this.
If this is something that should happen whenever you create a Post, then you should use active callbacks to achieve that :
class Post < ActiveRecord::Base
after_create do |post|
# create your association using post.id
end
end
or, you can write it like that also :
class Post < ActiveRecord::Base
after_create :after_create_post
def after_create_post
# create your association using self.id
end
end
Otherwise, if this is something specific to a controller action, you should simple do something like this :
class PostsController < ApplicationController
def create
#post = current_user.posts.build(params[:post])
# then use the #post.id to build your association. something like
#post.associations.build(:prop1 => 'value1', :prop2 => 'value2')
end
end
Hope this helps!

Rails 3: Passing belongs_to field in form, can't mass-assign protected attributes

I have two model classes, Patient and Prescription, with a belongs_to relationship:
class Prescription
belongs_to :patient
...
I have a form for creating new Prescription objects, and I want it to get the patient from a hidden field:
<%= form_for(#prescription) do |f| %>
...
<%= f.hidden_field :patient_id, :value => #patient.id %>
...
In the prescriptions controller I want to create a new prescription using the params I got from the form:
def create
#prescription = Prescription.new(params[:prescription])
...
Something is not working. I can see in the log that the patient id is being passed in the params, but it is not getting inserted into the db:
Started POST "/prescriptions" for 127.0.0.1 at 2011-05-13 14:59:00 +0200
Processing by PrescriptionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"h3rizbBoW069EfvQf6NyzH53k+g4o4XO61jeZ/GF6t0=", "prescription"=>{"medicine_name"=>"w", "dispense_date(1i)"=>"2011", "dispense_date(2i)"=>"5", "dispense_date(3i)"=>"13", "days_supply"=>"2", "patient_id"=>"1"}, "commit"=>"Create Prescription"}
WARNING: Can't mass-assign protected attributes: patient_id
Patient Load (0.2ms) SELECT "patients".* FROM "patients" WHERE "patients"."id" IS NULL LIMIT 1
AREL (0.4ms) INSERT INTO "prescriptions" ("medicine_name", "dispense_date", "days_supply", "patient_id", "created_at", "updated_at") VALUES ('w', '2011-05-13', 2, NULL, '2011-05-13 12:59:00.690434', '2011-05-13 12:59:00.690434')
What does the warning message about mass-assign protected attributes mean? How do I change the code so it works?
I think you have missed one of the great things about rails which would really help in this scenario. And that is the possibility to nest resources in the routing.
For example, if your routes.rb looks like this:
resources :patients do
resources :prescriptions
end
That would result in the url for your controller looking like /patients/:patient_id/prescriptions/ and the result of that is that since the patient_id is already existing in the url, you don't have to have any hidden form to store it. So in your PrescriptionsController, the create action could look like this:
def create
#patient = Patient.find(params[:patient_id])
#prescription = #patient.prescriptions.build(params[:prescription])
When you use the association to "build" the instance instead of directly with the model, it will automatically assign the patient_id for you.
This may not be the exact answer to your question but this is probably the way I would have done it.
'Cannot mass-assign' means you cannot assign a value automatically like this:
# In the examples below #prescription.patient_id will not be set/updated
#prescription = Prescription.new(params[:prescription])
#prescription.update_attributes(params[:prescription])
You can solve this by setting :patient_id as attr_accessible in your Prescription model. If you do this make sure you understand the security risks.
attr_accessible :patient_id
Or by assigning a value to patient_id directly:
#prescription.patient_id = some_value
I didn't put enough in my code snippets, above. It turns out the problem was due to this in my model:
class Prescription:
belongs_to :patient
attr_accessible :medicine_name, :dispense_date, :days_supply
So I didn't have patient on the list of attr_accessible, and this caused the error message. I don't really understand what the attr_accessible is needed for, and everything worked if I removed it.
Thankyou for your comments, especially the one about the nested resources, I will look into that.