Using radio buttons in a form to update database - ruby-on-rails-3

I have a deals controller that has a list of deals, I want each deal to have 3 radio buttons to toggle between "won, lost, pending". What would be the best approach in order to achieve this?
I have a couple of options, either create new entries in the deals table of type boolean, or create a different table connected to the deals table with a foreign key that has a boolean value for each option.
I'm a bit confused..
_deal_status.html.erb
<%= form_for #deal do |f| %>
State:
<label>
<%= f.radio_button :state, 'pending', :class => "radio" %>
Pending
</label>
<label>
<%= f.radio_button :state, 'won', :class => "radio" %>
Won
</label>
<label>
<%= f.radio_button :state, 'lost', :class => "radio" %>
Lost
</label>
<% end %>
this form is resetting the original #deal form to nil except for the radio buttons value.

Sounds like a state column is what you're looking for. If you add several boolean columns you'll have duplication of data as a deal cannot be in 2 of the given states at any time (won and lost at the same time shouldn't be possible). Look into State Machines. You can do more advanced stuff with a State Machine.
Although I highly recommend it, you don't need a State Machine. You can just add a string column state to your deals-table and use that column to keep track of the state of each deal.
$ rails g migration add_state_to_deals state:string
To get the the radio buttons in your form you can do:
<%= form_for #deal do |f| %>
...
State:
<label>
<%= f.radio_button :state, 'pending' %>
Pending
</label>
<label>
<%= f.radio_button :state, 'won' %>
Won
</label>
<label>
<%= f.radio_button :state, 'lost' %>
Lost
</label>
...
<% end %>
And have this validation and default value setter in your model:
class Deal < ActiveRecord::Base
...
validates_inclusion_of :state, in: %w{pending won lost}
after_initialize :set_default_state, if: :new_record?
private
def set_default_state
state = 'pending'
end
...
end

Related

Rails - SQL injection using .order to filter an index

In my index view, I'm iterating over a list of bookings.
Also, I added a dropdown menu with the option to sort by created_at: asc and created_at: desc.
index.html.erb
<div class="dropdown">
<button class="btn" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sort by
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<p> <%= link_to "ASC", sort: :asc %> </p>
<p> <%= link_to "DESC", sort: :desc %> </p>
</div>
</div>
<% #bookings.each do |booking| %>
<%= booking.address %>
<%= booking.created_at %>
<% end %>
This is the way I am sorting the #bookings in the controller:
booking_controller.rb
class Users::BookingsController < ApplicationController
def index
#bookings = current_user.bookings.order(created_at: params[:sort])
end
end
I'm not really sure if this is the best solution and if it has some vulnerability in terms of SQL injections...
Generally using params for order is unsafe, see https://rails-sqli.org/#order
You can use sanitize_sql_for_order to sanitaze input for ActiveRecord#order
Passing key/value pairs to order(created_at: params[:sort]) is safe. Rails validates the direction. If you give it an invalid direction it will raise ArgumentError: Direction "..." is invalid. It's been this way since the syntax was introduced in Rails 4.
Passing a string to order as in order("created_at #{params[:sort]}") could be exploited in Rails 5 and earlier. See Rails SQL Injection for details. Rails 6 now sanitizes order arguments and will raise an exception if it detects funny business.
Rails 6, in general, is more robust against SQL injection. But it's up to you to sanitize your inputs before passing them to anything which accepts raw SQL.
Your view is not turning the bookings into a drop down menu. Instead, it's just a bunch of text. As lurker suggested, use a function like collection_select to generate the select and option tags for you.
<%= form_for #user do |f| %>
<%= f.collection_select :booking_id, #bookings, :id, proc { |b| "#{b.address} #{b.created_at}" , prompt: true %>
<%= f.submit %>
<% end %>
To tidy that up a bit, you can add a method to Booking to produce the label you want and replace the proc.
class Booking
def dropdown_value
"#{address} #{created_at}"
end
end
<%= form_for #user do |f| %>
<%= f.collection_select :booking_id, #bookings, :id, :dropdown_value, prompt: true %>
<%= f.submit %>
<% end %>

Buttons to Update Attributes

I have 3 models: Restaurant/User/Reservation.
Reservation belongs_to both other models (the other two are has_many, through:).
I need a user to be able to make a reservation # a restaurant. I wanted to make a button to do it:
<h1>Restaurants#index</h1>
<% #all.each do |x| %>
<%= link_to x.name %><br>
<%= x.image %><br>
<%= x.current_capacity %>/<%= x.capacity %>
<p><%= submit_tag "Reserve?" %></p>
<% end %>
How can I design this functionality? I was originally thinking to have a column under Restaurants for "capacity" and "current_capacity" and worry about using time later. How can I get this button to set an instance of Restaurant to update its current_capacity?

Append/Update params to an existing form using form_for

Fairly new to rails, meaning I believe I have most of the basics down, but definitely still a student of the art.
What I'm trying to accomplish: append params to an existing form using form_for.
So, I need to find the id for an existing form and pass a new hash to this form using the JobsController to append several columns of data.
Steps are to select the job number from a select_box, add any changes/notes for that job using text_fields/check_boxes, then submit. Also view used is a partial, found in views/jobs.
Current problem, despite several attempts, is:
Overriding the default 'post' method --tried several combinations of the following: method => 'put', :action => update, :controller => :jobs
class Job < ActiveRecord::Base
attr_accessible :note, :twgid, :padd, :lnkbr, :txtwr, :datewr, :pinmiss, :headch, :name, :nucopy, :minrend, :majrend, :lnkwr, :gmail, :hotmail, :aol, :outlook, :note, :linkwr
belongs_to :form
end
JobsController:
( scaffolded )
View: (jobs/_form.html.erb)
<%= form_for #job do |f| %>
<fieldset>
<div class="form-horizontal">
<div class="control-group">
<span class="control-label">
<%= f.label :name, "TWGID" %></span>
<%= f.select :name, options_from_collection_for_select(Job.all, :name, :name), :prompt => 'Select' %>
</fieldset>
<fieldset>
<div class="control-group">
<span class="control-label">
<%= f.label :padd, "Add Padding?" %></span>
<%= f.check_box :padd %>
<%= f.hidden_field, :updated, :value => '1' %>
<% end %>
##Routes##
get 'signup', to: 'users#new', as: 'signup'
get 'login', to: 'sessions#new', as: 'login'
get 'logout', to: 'sessions#destroy', as: 'logout'
resources :sessions
resources :users
resources :forms
resources :projects
resources :jobs
root :to => 'forms#index'
I'm not 100% sure what your trying to do but let me take a crack at it.
Well, the first thing I see is that in update_job on the first line you try grabbing a Change object and putting it into #change. The problem is that your using #change.id which obviously hasn't been set yet. If you are getting the id through params you will want to use:
#change = Change.find(params[:id])
Second, you are trying to update an attribute of Job but have a Change object. This will not work.
What I think your trying to do overall is to build a form that updates a change and will also update its associations (jobs in this case). A quick link on how to do this is here:
Form Helpers <--- Search for Nested Attributes Examples
Here is the code I would suggest:
<%= form_for #change do |f| %>
<%= f.fields_for :job do |j| %>
Updated : <%= j.check_box :updated %>
<% end %>
<% end %>
This will put the updated option in params[:change][:jobs_attributes] and when you send params[:change] into your update like so, #change.update_attributes(params[:change]) it will update your job as well.

Multi-page Multi-Model Form in Rails

I'm trying to create a multi-page form that uses multiple models. I have an applicant and this applicant has more than one address (one to many relationship).
I would like the first page to contain information about the applicant, and then the page after that to have the form for the address(es)
This is what I have at the moment:
applicant.rb
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses
address.rb
belongs_to :applicant
applicants_controller.rb:
def new
session[:applicant_params] ||= {}
#applicant = Applicant.new(session[:applicant_params])
2.times do
#addresses=#applicant.addresses.build
end
session[:address_params] = #addresses.attributes
end
def create
session[:applicant_params].deep_merge!(params[:applicant]) if params[:applicant]
session[:address_params] ||= params[:address]
#applicant = Applicant.new( session[:applicant_params] )
#applicant.addresses.new(session[:address_params])
if params[:forward_button] or params[:back_button]
#applicant.current_step = session[:applicant_step]
if params[:back_button]
#applicant.previous_step
else
#applicant.next_step
end
session[:applicant_step]=#applicant.current_step
render "new"
else
.....
In the new view:
<%= form_for #applicant do |f| %>
<%= render "#{#applicant.current_step}_step", :f => f %>
<p><%= f.submit "Continue", :name => "forward_button" unless #applicant.last_step? %> </p>
<p><%= f.submit "Back", :name => "back_button" unless #applicant.first_step? %></p>
<% end %>
#applicant.current_step will be either address_step or applicant_step, and these are below:
_applicant_step.html.erb
<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name ,:width=>'10px', :size=>"20", :maxlength=>"20" %>
</div>
<div class="field">
<%= f.label :middle_name %><br />
<%= f.text_field :middle_name %>
</div>
....
_address_step.html.erb
<%= f.fields_for :addresses do |u| %>
<div class="field">
<%= u.label :address %><br />
<%= u.text_area :address , :cols=> 20, :rows=>5%>
</div>
<div class="field">
<%= u.label :telephone, "Telephone Number" %><br />
<%= u.text_field :telephone %>
</div>
...
And that's it. Now the problem I have is as follows, I want to save the address information as well as the applicant information. And I thought just by saying: #applicant.addresses.new then they will be included in the session information of the applicant, but they weren't (when I got the the address page, there were no fields!). So I created a new session variable to hold the address information. But I have a problem. Everytime I go from one page to the other (in the create action) a new address field (with all its attributes) is created and added to the form. So first I have one address, then I have 2 and so on. Am I going about this the wrong way? How can I have a multi-page form, with multiple models (that are related), and when going from one page to the next the data is not erased.. Until I eventually reach the last page where I could submit (and save) all the models..
I would be grateful if anyone could help..
Thank you.
You can certainly get this working, but (depending on the number of steps you need), it could get pretty convoluted.
Have you considered using a single new and create request on the server side, and using Javascript to break up the form into multiple steps on the client side? It seems like this could be a lot simpler. Your Rails application will behave more like a standard REST application, and you can break this up however you see fit on the client side.
I have not used it myself, but a jQuery plugin such as this one should do the trick:
http://thecodemine.org/

Rails 3 - how to implement changing case of descriptions based on a check_box_tag (not part of model) in before_save callback in external class

I have an rails 3 application where there are multiple registrations (diagnosis, patient, laboratory test, service, client, user, supplier). Initially these will be populated by seeding the database. The requirement is for the description codes to be mixed case (capitalised first word) when either
1. specified by the application (some configuration setting - yet to be determined)
2. specified by data entry user
At present I have a model, view & controller for Diagnosis which contains two fields:
1. code (always to be capitalised)
2. description (First word capitalised based on check_box_tag value)
Presently I am using a before_save callback in the model to implement the conversion, but I cannot get it to only work when the check_box_tag is not selected i.e. its ignoring the check_box_tag.
I have tried changing the check_box_tag to a check_box adding an attr_assessor to the model (but not the sqlite3 db as it is not required to be stored).
This didn't work either.
How do I accomplish this? How do I override the option to use a checkbox from an internal application configuration file which results in either the checkbox being 'unavailable' or not visible if the application configuration specifies not user selectable?
Model (diagnosis.rb)
require 'DescriptionHelper'
class Diagnosis < ActiveRecord::Base
attr_accessible :code, description
string_correct_case = DescriptionHelper.new([:code, :description])
validates :code, :presence => true, :length => { :minimum => 4, :maximum => 4 }
validates :description, :presence => true
before_save string_correct_case
end
Callback in DescriptionHelper.rb
class DescriptionHelper
def initialize(attribute)
#attrs_to_manage = attribute
end
def before_save(record)
#attrs_to_manage.each do |attribute|
record.send("#{attribute}=", capitaliseWords(record.send("#{attribute}")))
end
end
private
def capitaliseWords(value)
value = value.mb_chars.downcase.to_s.gsub(/\b\w/) { |first| first.upcase }
end
end
Controller (diagnoses_controller.rb)
class DiagnosesController < ApplicationController
def new
#diagnosis = Diagnosis.new
end
def create
#diagnosis = Diagnosis.new(params[:diagnosis])
if #diagnosis.save
flash[:notice] = "Diagnosis created with params [#{#diagnosis.attributes.inspect}" #for debugging, once fixed will be just 'Diagnosis created.'
redirect_to #diagnosis
else
flash[:alert] = "Diagnosis not created."
render :action => "new"
end
end
.. other controller actions - edit, show, destroy
end
View (_form.html.erb)
<%= form_for(#daignosis) do |f| %>
<div class="field">
<%= f.label :code %>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= check_box_tag("diagnosis_desc_dont_convert", 1, false) %><%= f.label "Leave as entered" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When this runs currently the check_box_tag is ignored.
When adding in the model an attar_assessor :description_correctcase and changing the view to use f.check_box 'description_correctcase' this is still ignored.
How does one get this to work?
Thanks in advance from a rails aspiring developer.
Finally got a solution to the problem, after reading and re-reading various SO solutions to component parts of my question. I'm not sure its correct in terms of rails, but it works.
If you can offer me a better solution I would certainly learn from this.
Here is my solution.
Model (diagnosis.rb)
require 'DescriptionHelper'
class Diagnosis < ActiveRecord::Base
attr_accessor :do_not_correctcase
attr_accessible :code, :description, :do_not_correctcase
before_save DescriptionHelper.new([:code, :description]), :if =>
lambda { |d| d.do_not_correctcase.to_s == '0' }
validates :code, :presence => true, :length => { :minimum => 4, :maximum => 4 }
validates :description, :presence => true
end
This I referenced from the following SO solution - https://stackoverflow.com/a/6388691/1108010
Controller (diagnoses_controller.rb)
class DiagnosesController < ApplicationController
def new
#diagnosis = Diagnosis.new
end
def create
#diagnosis = Diagnosis.new(params[:diagnosis])
#diagnosis.do_not_correctcase = params[:diagnosis][:do_not_correctcase]
logger.debug "New diagnoses: #{#diagnosis.attributes.inspect}"
logger.debug "Diagnosis should be valid: #{#diagnosis.valid?}"
logger.debug "code has value #{params[:code]}"
if #diagnosis.save
flash[:notice] = "Diagnosis created with params [#{#diagnosis.attributes.inspect}" #for debugging
redirect_to #diagnosis
else
flash[:alert] = "Diagnosis not created."
render :action => "new"
end
end
.. other controller actions - edit, show, destroy
end
I also changed the view to replace the check_box_tag with a check_box.
View (_form.html.erb)
<%= form_for(#daignosis) do |f| %>
<div class="field">
<%= f.label :code %>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.check_box 'do_not_correctcase' %><%= f.label "Leave as entered" %><br />
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
So despite getting this to work I'm not clear on are the following:
When inspecting the attributes with "#{#diagnosis.attributes.inspect}".
I assume that the reason the attr_accessor variable is not included in the New diagnosis output is that it is not part of the database table and therefore Active Reocrd does not instanciate it as part of the new record with #diagnosis.new
Could someone be kind enough to confirm that.
Why does the log have no value for logger.debug "code has value #{params[:code]}"? What causes the params[:code] to be null in the logger output?
Logfile contained the following entry:
Started POST "/diagnoses" for 127.0.0.1 at 2012-03-05 09:36:38 +0000
Processing by DiagnosesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RW/mzkhavGeaIW0hVLn0ortTnbCDlrX+FfzH4neLLsA=", "diagnosis"=>{"code"=>"tt02", "description"=>"description for tt02", "do_not_correctcase"=>"1"}, "commit"=>"Create Diagnosis"}
New diagnosis: {"code"=>"tt02", "created_at"=>nil, "description"=>"description for tt02", "updated_at"=>nil}
Diagnosis should be valid: true
code has value
I would dearly like to know what is the correct way to do all this, as I feel this is not very DRY or clean.