Passing ID Value to a Controller, Getting Mass Assignment Security Error - ruby-on-rails

I have a message model and a user model. my message belongs_to my user and user has_many messages.
I'm trying to allow a user to private message another user while on their public profile page (their show template). I have tried a number of attempts, but I ultimately run back into the issue of requiring an ID to be attr_accessible (which I heard is bad to do). Am I doing something wrong?
My message model, I have :user_id (which is the current user, aka a sending_from ID), :to_id, :content.
When I'm looking at a users profile page, on the show template I have
<%= form_for([current_user, #message]) do |f| %>
<%= f.hidden_field :to_id, :value => #user.id %>
<div class="field">
<%= f.text_area :content, placeholder: "Send a private message..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
In my user show action, I have
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
if user_signed_in?
#message = current_user.messages.build(params[:messages], to_id: #user.id)
end
end
when the form submits, it goes to my message create action
def create
#message = current_user.messages.build(params[:message])
redirect_to user_path(params[:message][:to_id])
end
However, I always get the error
`Can't mass-assign protected attributes: to_id`
It seems like I can fix it by making :to_id attr_accessible, however I have heard it is not very safe to do so. Am I doing something wrong? This issue has been killing me.
Any help would be appreciated. Thanks

Making to_id accessible is fine. But if you don't want that error just fix it like this:
def create
#message = current_user.messages.build
#message.to_id = params[:message][:to_id]
# manually assign whatever other params you need to
redirect_to user_path(params[:message][:to_id])
end
Mass assignment just means you can't use update_attributes, you can still use model.attribute=. The reason for doing it that way might be to add additional whitelisting parameters, such as:
def create
safe_params = params[:model].slice(:safe_attr1,:safe_attr2)
#model = Model.new(safe_params)
whitelist = ['some_safe_string','another_safe_string']
if whitelist.include?(params[:model][:dangerous])
#model.dangerous_attribute = params[:model][:dangerous]
end
#model.save
redirect_to #model
end

Related

Storing id from select in ruby variable

I'm working on a RoR backend for a big mobile app, currently with the admin panel.
I have two models: Activity and Deal, joined by HMT ActivitiesDeal. The join is tested both ways in rails console and works like a charm.
Activity is the model the app is built around, so admins need to be able to add deals to activity from the "Edit activity" form in some intuitive way.
I tried this for creating activities_deal:
<%=select("deal", #deal_id, Deal.all.collect {|d| [d.title, d.id]}, {})%>
<%= link_to "Add", link_activity_deal_path(activity_id: #activity.id, deal_id: #deal_id), method:'post' %>
But it doesn't work as I thought. Any ideas on how to send the correct deal_id to link_activity_deal_path? This seems like a problem that has been solved many times, but I can' find anything that fits.
ActivitiesDealsController:
class ActivitiesDealsController < ApplicationController
def create
#activity = Activity.find(params[:activity_id])
render file: 'public/404.html' and return unless #activity && Deal.find(params[:deal_id])
#activity_deal = ActivitiesDeal.new
#activity_deal.activity_id = params[:activity_id]
#activity_deal.deal_id = params[:deal_id]
if #activity_deal.save
redirect_to proc {activity_url #activity}
end
render file: 'public/500.html'
end
def destroy
p params
#activity = Activity.find(params[:activity_id])
render file: 'public/404.html' and return unless #activity
#activity_deal = ActivitiesDeal.where("activity_id == ? AND deal_id == ?", params[:activity_id], params[:deal_id])
render file: 'public/404.html' and return unless #activity_deal
ActivitiesDeal.destroy(#activity_deal)
redirect_to proc {activity_url #activity}
end
end
Fixed the problem by making a form_for outside of the edit page.
If anyone needs the code:
<%= form_for #activity, as: :deal, :url => link_activity_deal_path(activity_id: #activity.id), method:'post' do |f|%>
<%= f.collection_select :id, #deals, :id, :title %>
<%= f.submit "Add Deal", class: "btn btn-primary" %>

Ror find record in db and show it to user

This is my first ror app.
I have main page: home.html.erb
I have form there.
<%= form_for(#lead ,:html => {:class => 'check_form'}) do |f| %>
<%= f.text_field :phone, placeholder: 'phone' %>
<%= f.submit "Check car status", class: "btn btn-large btn-primary" %>
<% end %>
Backstory: a customer(I call him Lead can input his phone number and check status of his car which is being repaired now.)
Right now this view home.html.erbis served by static_pages_controller
class StaticPagesController < ApplicationController
def home
#lead = Lead.new()
end
def help
end
def about
end
def contact
end
end
I have also LeadsController
class LeadsController < ApplicationController
*some code*
def create
#lead = Lead.new(lead_params)
if #lead.save
#sign_in #lead
flash[:success] = "Request successfully created!"
redirect_to #lead
else
render 'new'
end
end
* some code
end
What I want to do when user inputs his phone number to find lead in database with the same phone number and show repair status to user.
So back to my problem:
I know how to find lead by phone like this Lead.find(params[:id])
But where to write this code? I need to find lead by phone and then print it to screen. How can I do this?
What I want to do when user inputs his phone number to find lead in
database with the same phone number and show repair status to user.
Currently your form serves the wrong purpose. This requires a form with GET request. I'll be doing it by declaring a custom route like below
get :check_lead_car_status, to: 'static_pages#check_lead_car_status', as: 'check_lead_car_status'
And in the static_pages#check_lead_car_status
def check_lead_car_status
#lead = Lead.find_by(phone: params[:phone]
end
And modify the existing form like below
<%= form_tag check_lead_car_status_path, :method => :get do %>
<%= text_field_tag :phone, placeholder: 'phone' %>
<%= submit_tag "Check car status", class: "btn btn-large btn-primary" %>
<% end %>
And a page check_lead_car_status.html.erb with the below code
The Status of the Car is: <%= #lead.status %>
youre redirecting to #lead which means should be the show path in the lead controller. which means you need to put that logic in a method called show in your Lead controller
then in your view (views/leads/show.html.erb) you can access that variable
edit:
if all youre trying to do is query by a different parameter, then you should look into the active record query interface. specifically section 1.1.5
Lead.find_by(phone: params[:phone])

nested associations in form not updating properly

I have the following situation. A contact has_many lead_profiles. I have a new lead_profile form, where the user enters information about the lead_profile and contact. However, I am using a jquery autofill plugin, in that if the contact already exists, the user can select it and then a hidden input id attribute is created to indicate that it's no longer a new contact:
# controller
def new
#lead_profile = LeadProfile.new
#contact = #lead_profile.build_contact name: "Donato"
end
# view
<%= form_for #lead_profile do |f| %>
<%= f.fields_for :contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
<% end %>
# javascript
$('#lead_profile_contact_attributes_address').bind('typeahead:selected', function(obj, datum, name) {
var $parent = $("#lead_profile_contact_attributes_address").closest(".form-group");
$parent.after('<input name="lead_profile[contact_attributes][id]" type="hidden" value="' + contact.id + '">')
});
Now when the form is submit to create, I need to determine if the contact exists or not. Note that if I didn't do this, and sent an id attribute for contact back to server, Rails raises an exception "Couldn't find Contact with ID=10 for LeadProfile with ID=". So I must override the contact_attributes= call:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
Above, I determine if the contact is a new contact or an existing one (which would be the case if the user populates it from javascript autocomplete).
All seems to work fine. If the contact is new, a new contact is created in database with attributes from params hash. If contact is existing, the existing contact is updated with attributes from form. And in both cases, the lead_profiles record is associated with that contact.
But now a problem occurs when I want to update the lead_profile. I use the same form for update as I did for new:
<%= form_for #lead_profile do |f| %>
<%= f.fields_for :contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
<% end %>
If I update the contact info in the lead_profile form, the contact is not updated in the database, because of this override that I needed:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
If I remove that, then the contact would be updated, however then I would get the other issue when creating a new contact:
Couldn't find Contact with ID=10 for LeadProfile with ID=
I really want to save this two models together in a single form, and Rails shouldn't restrict me on that. What can I do at this point?
Ok, so I have one solution working. Remember that when you do an update on a model, it automatically does an update on associations as soon as you invoke save on the model. By default, it will update its association attributes based on the input in the fields_for nested form. However, as soon as you override that default and invoke find, then obviously nothing will be updated on the association:
def contact_attributes=(params)
if params[:id].present?
self.contact = Contact.find(params[:id])
else
self.contact = Contact.new params
end
end
The solution is to somehow distinguish when you are updating a model and when you are dynamically building an existing model association through the form via autocomplete. I used a virtual attribute for that purpose. If the field is being dynamicalyl generated by autocomplete, this is not a rails default and you have to handle it yourself:
model Contact < ActiveRecord::Base
attr_accessor :autocompleted
end
model LeadProfile < ActiveRecord::Base
def contact_attributes=(params)
# this contact model was autocompleted via javascript
if params[:id].present? && params[:autocompleted].present?
self.contact = Contact.find(params[:id])
else
# self.contact = Contact.new params
super
end
end
In all other cases, call super and let Rails do its default behavior.
Just a guess try adding the contact instance in your fields_for call i.e.
<%= f.fields_for :contact,#contact do |builder| %>
<%= builder.text_field :name %>
<%= builder.text_field :address %>
<% end %>
Let me know if that works. I've often had random issues with nested forms and sometimes explicitly setting the target object of the nested form solves the problem

Cannot find restaurant without id, how to submit form and pass parameters

I am attempting to allow a user to create a campaign given they have selected a certain restaurant,
the models are:
class Restaurant < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
has_many :campaigns, dependent: :destroy
end
class Campaign < ActiveRecord::Base
belongs_to :restaurant
end
That said, I have the methods in the Campaigns controller to create a new campaign and then to build (below).
def new
if signed_in?
# create new campaign
#campaign = Restaurant.find(params[:id]).campaigns.new
else
redirect_to signin_path
end
end
def create
#campaign = Restaurant.find(params[:restaurant_id]).campaigns.build(campaign_params)
if #campaign.save
flash[:success] = "Campaign created!"
redirect_to current_user
else
render 'new'
end
end
The user progress in the app essentially is: While on the restaurant show page the user clicks on create a campaign, accessing the new method in campaign controller.
The view for a new campaign, without the partial, is below and as can be seen it accepts the #campaign from new.
<h1>New Campaign</h1>
<div class="row">
<div class="span6 offset3">
<%= nested_form_for #campaign do |f| %>
<%= render 'parameters', f: f %>
<div class="span6 pull-right" style="text-align:right">
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
</div>
As I attempt to submit the form, the error arises with the campaign controllers create method at #campaign = Restaurant.find(params[:restaurant_id]).campaigns.build(campaign_params) noting Couldn't find Restaurant without an ID
As displayed, the campaign belongs to a restaurant with the id in campaign models field :restaurant_id
How can I make this work to pass the restaurants id so the campaign can be created?
To add a parameter to a form, use the hidden_field helper just above the submit tag:
<%= f.hidden_field :restaurant_id, #campaign.restaurant.id %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
This should be available in the create method of your controller as:
params["campaign"]["restaurant_id"]
If you for some reason want it to come in simply as params["id"], you would need to use a hidden_field_tag like so:
<%= hidden_field_tag :id, #campaign.restaurant.id %>
Side note - for debugging, you should use the debugging tool called "pry". This will change your life.
Simply add to your Gemfile:
gem 'pry'
then bundle and restart your server:
bundle
rails s
Then put a 'binding.pry' anywhere you want (in a view, model, helper, or controller) - the server will pause at the place where you put the binding.pry and you can poke around in the server console.
I would put the binding.pry right inside your create method:
def create
binding.pry
# rest of code here
end
Then submit the form like normal, and check your server screen in the terminal... you should see the Pry prompt.
Type:
params
to see all of the parameters coming through the form submit.
to exit, type exit or ctrl+c
Remember to remove the binding.pry when you are done!!!

Rails: form_for security hole?

I have a user model with :name and :is_admin attributes. You should not change is_admin value. If you write a form in which any user may edit their name:
<%= form_for #user %>
<%= f.label :given_name %>
<%= f.text_field :given_name %>
<%= f.submit "Update" %>
<% end %>
Are you opening up a security hole?
Kind regards,
No.
This is because parameters are protected when they come into the controller by the strong parameters feature within Rails. In controllers now you define a create action like this:
def create
#user = User.new(user_params)
...
end
That user_params method looks like this:
def user_params
params.require(:user).permit(:name)
end
This code will permit the name parameter from the user parameters and outright reject everything else.
This is talked about in this section of the Getting Started guide.

Resources