Am I using the rails form properly? - ruby-on-rails

I feel like I have no idea what I'm doing.. I have a vague Idea. I'm hoping I did this all right so far.
Any way you can see to refactor this would be greatly appreciated.
One thing I noticed it does wrong is it won't load the proper options that were previously submitted if there is an error and it posts to the same URL. The text inputs seem to load the previous value but the select and the radio buttons reset to the default every submit.
ResourcesController#new
def new
#resource = Resource.new
#title = "Submit Resource"
#categories = Category.all
end
ResourcesController#create (notice I have #categories = Category.all in both... according to DRY im not sure where else it should go, or it only works on the first form submit.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
category_ids = #categories.map { |c| c[1] }
if #resource.valid? and category_ids.include? params[:category_id]
#resource.cost = params[:cost]
#resource.category_id = params[:category_id]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
Resource.rb (model)
# == Schema Information
#
# Table name: resources
#
# id :integer not null, primary key
# upvotes :integer default(0)
# downvotes :integer default(0)
# url :string(255)
# title :string(255)
# cost :integer default(0)
# description :text
# flags :integer
# category_id :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Resource < ActiveRecord::Base
belongs_to :category
belongs_to :user
has_many :favorites
has_many :resource_tags
has_many :tags, :through => :resource_tags
attr_accessible :url, :title, :cost, :description, :category_id, :user_id
# Pseudo-Enum
COST = [:free, :paid, :both]
url_regex = /^(?:http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/.*)?$/ix
validates :url, :presence => true,
:format => { :with => url_regex,
:message => "must be valid"},
:uniqueness => { :case_sensitive => false,
:message => "has already been submitted"}
validates :title, :presence => true,
:length => { :within => 6..75 }
validates :cost, :presence => true
validates :description, :presence => true,
:length => { :within => 25..200 }
validates :category_id, :presence => true,
:format => { :with => /\d+/ }
validates :user_id, :presence => true,
:format => { :with => /\d+/ }
def cost
COST[read_attribute(:cost)]
end
def cost=(value)
write_attribute(:cost, COST.index(value.downcase.to_sym))
end
def category_id
read_attribute(:category_id).to_i
end
def category_id=(value)
write_attribute(:category_id, value.to_i)
end
end
My view file for the Resource#new form
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]})) %>
</div>
Last Q: i havent worked with the user_id field yet. This is going to be pulled from devise and will associate a User with a submitted resource. But how do I assign this without making some sort of input, like a hidden input. Would this go on behind the scenes in the controller?

To your last question:
devise adds a current_user method which is the logged in user. So if a user has multiple resources you could do something like:
#resource = current_user.resources.new(params[:resource])
First question:
When a form is rendered it is done so based on the #resource & #categories variables. When you post the form the create action is called which creates a new #resource. If the save fails for whatever reason the form is rerendered using the new #resource variable. The problem you have is that #resource.category is not set when you show the form again. So you'll have to do this before the is_valid? check.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
But the real problem is with your form. It should nest the category_id in the resource params so that the category is set when you do Resource.new(params[:resource]).
Check the POST request body in your console or something and see if it's nested in the resource or not. I don't know the exact syntax for it but if you change this you can drop the #resource.category = Category.find line.

To piggyback on Sandip, you can dry up your actions by using a before_filter
class ResourcesController < ApplicationController
before_filter :load_categories, :only => [:show, :create]
def new
#resource = Resource.new
end
def create
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
private
def load_categories
#categories = Category.all
end
end
also if you plan on sticking #title inside of your application layout, I would change #title in your view to:
yield(:title) || 'My Site'
and on the appropriate pages use:
content_for(:title) do
Submit Resource
It will default to 'My Site' unless otherwise specified.

Looks like there is problem with create action
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
if #categories.collect(&:id).include?(params[:category_id].to_i)
#resource.category_id = params[:category_id]
end
#resource.user = current_user
if #resource.valid?
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
view
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]}, :selected => #resource.category_id)) %>
</div>

Related

ensure fields hold values as more fields are added rails

I have a form for JobDeliveryCost where a user can add delivery costs. Every time the user adds one, an additional field is created to add another. at the moment, I have a form and I display the fields.
-jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery].flatten : [#new_delivery])
-jdc_array.each do |jdc|
= simple_form_for [:admin, #job, #new_delivery] do |f|
%tr
%td
= jdc.timing
= f.input :timing, collection: JobDeliveryCost::TIMINGS, :include_blank => "please select"
%td
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
%td
-if jdc.new_record?
=f.submit "add"
-else
%td
= jdc.cost_per_unit
= f.input :cost_per_unit
%td
= jdc.quantity
= f.input :quantity
Instead of displaying the inputted value above each form entry, how do I get the fields to hold their value instead?
Also, how would I display the value of this
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
as it is a child attribute of the DeliveryCost model?
For extra enfo I have added my controller and relevant models
class Admin::JobDeliveryCostsController < ApplicationController
before_action :set_job
def index
# raise #job.inspect
if get_deliverable
#jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery] : [#new_delivery])
# raise #jdc_array.inspect
#new_delivery = #deliverable.job_delivery_costs.build
end
set_job_delivery_cost
end
def create
if #job
#job_delivery_cost = JobDeliveryCost.new(job_delivery_cost_params)
#job_delivery_cost.job = #job
if #job_delivery_cost.quantity.nil?
#job_delivery_cost.quantity = 1
end
# raise #job_delivery_cost.inspect
if #job_delivery_cost.save
flash[:success] = "Delivery Costs Added"
else
flash[:error] = "Delivery Costs not Added"
end
else
flash[:error] = "Couldn't find the Job."
end
redirect_to admin_job_job_delivery_costs_path(#job)
end
def destroy
set_job_delivery_cost
if #job.present? && #job_delivery_cost.present?
#job_delivery_cost.destroy
flash[:success] = "Job delivery cost removed"
else
flash[:error] = "Couldn't find the record"
end
redirect_to admin_job_job_products_path(#job)
end
private
def set_job
#job = Job.find_by(id: params[:job_id])
end
def set_job_delivery_cost
#job_delivery_cost ||= JobDeliveryCost.find_by(id: params[:id])
end
def job_delivery_cost_params
params.require(:job_delivery_cost).permit!
end
def get_deliverable
return #deliverable if #deliverable
if params[:contact_id].present?
#deliverable = Contact.find_by(id: params[:contact_id])
elsif params[:client_id].present?
#deliverable = Client.find_by(id: params[:client_id])
elsif params[:job_id].present?
#deliverable = Job.find_by(id: params[:job_id])
end
#deliverable
end
end
delivery_cost.rb
# == Schema Information
#
# Table name: delivery_costs
#
# id :integer not null, primary key
# title :string(255)
# unit :string(255)
# cost_per_unit :float
# created_at :datetime
# updated_at :datetime
#
class DeliveryCost < ActiveRecord::Base
UNIT_DAY='day'
UNIT_HOUR='hour'
UNIT_MILE='mile'
UNITS=[UNIT_DAY,UNIT_HOUR,UNIT_MILE]
has_many :job_delivery_costs
has_many :jobs, through: :job_delivery_costs
validates :cost_per_unit, presence: true
validates :unit, inclusion: UNITS
validates :title, presence: true
before_destroy :survive_if_jobs
private
def survive_if_jobs
jobs.empty?
end
end
JobDeliveryCost
# == Schema Information
#
# Table name: job_delivery_costs
#
# id :integer not null, primary key
# job_id :integer
# delivery_cost_id :integer
# cost_per_unit :float
# quantity :integer
# timing :string(255)
# created_at :datetime
# updated_at :datetime
#
class JobDeliveryCost < ActiveRecord::Base
TIMING_INSTALL='install'
TIMING_BREAKDOWN='breakdown'
TIMINGS=[TIMING_INSTALL,TIMING_BREAKDOWN]
belongs_to :delivery_cost
belongs_to :job
validates :quantity, presence: true, numericality: {greater_than_or_equal_to:1}
validates :timing, inclusion: TIMINGS
#validates :cost_per_unit, presence: true
validate :validate_cost_per_unit
validate :check_associates
# validate :quantity_default
before_save :init
private
def check_associates
associated_object_exists DeliveryCost, :delivery_cost_id
associated_object_exists Job, :job_id
end
def validate_cost_per_unit
if delivery_cost and cost_per_unit.blank?
self.cost_per_unit=delivery_cost.cost_per_unit
end
return false if cost_per_unit.blank?
end
def init
if self.quantity.nil?
self.quantity = 1
end
end
end
If I understand correctly, you are going to want to use javascript to solve your problem. There is an excellent railscast that discusses nested model forms but it also shows you how to build javascript for dynamically adding field elements with links.
Instead of links, you will most likely want to register an onChange event (meaning the user changed data in the field) to add another field.

getting null value in my table?

I am creating employee pay roll application. In that i am having two models one is employee and another is salary. I have added foreign key to get the employee_id in salaries table and to get the employee name in _form.html.erb(salaries) using the gem 'rails3-jquery-autocomplete'. I have followed all the steps in that gem. But foreign key relationship is not working. I am getting null value in the field employee_id in salaries table.
'
Here i have attached my model,view,controller,routes and everything. Kindly check it.
My Models
** Salary.rb**
class Salary < ActiveRecord::Base
belongs_to :employee, :foreign_key => "employee_id"
attr_accessible :basic, :da, :effective_from, :effective_to, :employeesalary, :hra, :ca, :sa, :employee_id
end
Employee.rb
class Employee < ActiveRecord::Base
has_many :salaries, :foreign_key => "employee_id"
attr_accessible :address, :age, :branch, :city, :date_of_birth, :designation, :employee_end_date, :employee_start_date, :gender, :name
end
My Controllers
salaries_controller.rb
class SalariesController < ApplicationController
autocomplete :employee, :name, :display_value => :name, :full => true
def new
#salary = Salary.new
end
def create
#salary = Salary.new(params[:salary])
if #salary.save
flash[:success] = "Created a new month salary!"
redirect_to salaries_path
else
flash[:error] = "Couldn't Create salary"
render 'new'
end
end
def index
#salary = Salary.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
def show
#salary = Salary.find(params[:id])
#employee =Employee.all
end
end
employees_controller.rb
class EmployeesController < ApplicationController
def new
#employee = Employee.new
#salary = #employee.salaries.build
end
def create
#employee = Employee.new(params[:employee])
if #employee.save
flash[:success] = "Created Employee Successfully!"
redirect_to employee_path(#employee)
else
flash[:error] = "Couldn't Create a Employee"
render 'new'
end
end
def index
#employee = Employee.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
def show
#employee = Employee.find(params[:id])
end
end
routes.rb
resources :salaries do
get :autocomplete_employee_name, :on => :collection
end
resources :employees
_form.html.erb(salaries)
<%= form_for #salary do |f| %>
<%= f.hidden_field :employee_id, :id => "real_employee_id" %>
<span class="help-block">Enter the Employee name</span>
<%= autocomplete_field_tag 'throwaway_employee', '',
autocomplete_employee_name_salaries_path,
:size => 75, :id_element => '#real_employee_id' %>
application.js
//= require jquery
//= require jquery_ujs
//= require autocomplete-rails
//= require_tree .
I have checked throughly but can't find the error. Kindly help me to get out of this issue.
Thanks in advance...
I had a play with this code tonight and I think I know what might be happening.
When you start typing in an employee name a green list of names pops up. If you just hit enter straight away the form takes whatever you have typed so far and uses it to look up the name. This fails because the name is incomplete. As you have no validation on the employee id the form works and a null employee id is saved.
What you need to do is to use the down arrow key or mouse to select one of the names. When you do that the name will change to light green and the text in the text box will be replaced with the full name.
Then when you submit the form the match is found and everything works!
So your code works, you are just using the GUI incorrectly.
You should also add this line to your salary.rb file:
validates :employee_id, presence: true
That's my theory. Please try it out and let us know how you go.

how to: Rails respond_to json using exact same rabl template?

I am have two actions of which renders eventually should output json using the same rabl template, but at the moment, they each have a template with their own name
dashboard.json.rabl and batch_create.json.rabl
they are the exact same, how can I specify in the batch_create template to use the dashboard's template?
Thank you!
EDIT #including controller's two actions and their rabl views
line_items_controller.rb
def dashboard
#line_items ||= LineItem.limit(1000).all.to_a
pids = [1,2,3,4,5]
#projects = Project.only(:id, :name).where(:_id.in => pids).to_a
#users = User.only(:first, :last, :role, :company_id).all.to_a
#companies= Company.where(:_id.in => #users.map(&:company_id)).to_a
#specs = Spec.where(:specable_id.in => pids).to_a
spec_ids= #specs.map { |e| e.id }
#contact_infos = ContactInfo.where(:infoable_id.in => spec_ids).to_a end
gon.rabl
respond_to do |format|
format.html
format.json
end
end
def batch_create
#line_items = LineItem.where(:_id.in => params[:line_item_ids]).to_a
# else same as #dashboard
end
app/views/line_items/dashboard.json.rabl SAME AS app/views/line_items/batch_create.json.rabl
object false
child #projects => :projects do
attributes :id, :name
end
child #companies => :companies do
attributes :id, :name
end
child #users => :users do
attributes :id, :full, :company_id
end
child #specs => :specs do
attributes :id, :style, :due_at_note
end
child #contact_infos => :contact_infos do
attributes :info, :infoable_id
end
child #line_items do
attributes :id, :title, :dashboard_length, :dashboard_created_at, :original_file_url, :project_id
end
Have you tried using this in the batch_create.json.rabl:
extends '<insert_object_name>/dashboard'

Hiding Rails Model Attributes

I have a controller for an API that looks like this:
def index
respond_to do |format|
format.json { render :json => #groups.to_json(:only => [:id, :name, :description, :created_at, :updated_at])}
end
end
def show
respond_to do |format|
format.json { render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at]) }
end
end
# #todo add store to item
def create
if #group.save
render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at])
else
render :status => 406
end
end
def update
if #group.update_attributes(params[:group])
render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at])
else
render :status => 406
end
end
def destroy
#group.destroy
render :text => ""
end
As you can see, I'm repeating my self a lot. I'd love to make these (and only these) attributes available by way of the model, but couldn't find a fitting solution. Is there anything to protect attributes from mass writing? Or do I possibly mean mass reading?
As noted in comments below I want to have a model with attributes, name and i_am_private. When I render that model as json - render :json => #model - I want only name to show up.
Ruby 1.8.7
Rails 3
How about overriding as_json method in your Group model?
class Group < ActiveRecord:Base
...
def as_json(options={})
{
:id => id,
:name => name,
:description => description,
:created_at => created_at,
:updated_at => updated_at
}
end
end
To prevent mass assignment, add the following to your model:
attr_accessible :attr1, :attr2, :attr3
where attr1, attr2, attr3 and so on are the attributes you want to allow for mass assignment, the rest of the attributes for that model will not be allowed for mass assignment.

virtual attribute problems with undefined methods

I've used Virtual attributes in the past but I can't seem to get past this, and I know the answer is probably staring me in the face.
I have a model like so:
model Confirmation.rb
class Confirmation < ActiveRecord::Base
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation_token
def confirmation_token
confirmation.confirmation_token if confirmation
end
def confirmation_token=(token)
self.confirmation = Booking.find_by_confirmation_token(token)
end
end
Your average scaffold controller for
confirmations_controller.rb
def new
#confirmation = Confirmation.new(:confirmation_token => params[:confirmation_token])
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #confirmation }
end
end
new.html.erb
<h1>New confirmation</h1>
<% form_for(#confirmation) do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :confirmation_token %>
...
routes.rb
map.confirmation "confirmation/:confirmation_token", :controller => "confirmations", :action => "new"
map.resources :confirmations
error
undefined method `confirmation=' for #
In the console Booking.find_by_confirmation_token(token) with a given token works perfectly fine.
Any ideas? suggestions?
What you really need is attr_accessor :confirmation. There's a difference between attr_accessible and attr_accessor.
attr_accessor :confirmation
is same as
def confirmation
#confirmation
end
def confirmation=(value)
#confirmation = value
end
Now since it's such a common pattern ruby introduced helper methods for that.
Attr_accesible on the other hand is rails method, which marks that certain fields can be mass updated.
I think it should be either:
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
Or you should uncomment attr_accessible :confirmation or define #confirmation and #confirmation=.
class Confirmation < ActiveRecord::Base
belongs_to :bookings
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation
def confirmation_token
#confirmation.confirmation_token if #confirmation
end
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
end
this worked... however just uncovering the attr_accessible :confirmation, did not. self.confirmation still returned undefined method...

Resources