How to remove a field from params[:something] - ruby-on-rails

My registration form, which is a form for the Users model, takes a string value for company. However, I have just made a change such that users belongs_to companies. Therefore, I need to pass an object of Company to the Users model.
I want to use the string value from the form to obtain the an object of Company:
#user.company = Company.find_by_name(params[:company])
I believe the above works, however the form is passing the :company (which is string) into the model when I call:
#user = User.new(params[:user])
Therefore, I want to know (and cannot find how) to remove the :company param before passing it to the User model.

Rails 4/5 - edited answer
(see comments)
Since this question was written newer versions of Rails have added the extract! and except eg:
new_params = params.except[the one I wish to remove]
This is a safer way to 'grab' all the params you need into a copy WITHOUT destroying the original passed in params (which is NOT a good thing to do as it will make debugging and maintenance of your code very hard over time).
Or you could just pass directly without copying eg:
#person.update(params[:person].except(:admin))
The extract! (has the ! bang operator) will modify the original so use with more care!
Original Answer
You can remove a key/value pair from a Hash using Hash#delete:
params.delete :company
If it's contained in params[:user], then you'd use this:
params[:user].delete :company

You should probably be using hash.except
class MyController < ApplicationController
def explore_session_params
params[:explore_session].except(:account_id, :creator)
end
end
It accomplishes 2 things: allows you to exclude more than 1 key at a time, and doesn't modify the original hash.

The correct way to achieve this is using strong_params
class UsersController < ApplicationController
def create
#user = User.new(user_params)
end
private
def user_params
params.require(:user).permit(:name, :age)
end
end
This way you have more control over which params should be passed to model

respond_to do |format|
if params[:company].present?
format.html { redirect_to(:controller => :shopping, :action => :index) }
else
format.html
end
end
this will remove params from the url

Rails 5+: Use the handy extract! method with strong params!
The extract! method removes the desired variable from the Parameters object (docs) and returns a new ActionController::Parameters object. This allows you to handle params properly (i.e. with strong params) and deal with the extracted variable separately.
Example:
# Request { user: { company: 'a', name: 'b', age: 100 } }
# this line removes company from params
company = params.require(:user).extract!(:company)
# note: the value of the new variable `company` is { company: 'a' }
# since extract! returns an instance of ActionController::Parameters
# we permit :name and :age, and receive no errors or warnings since
# company has been removed from params
params.require(:user).permit(:name, :age)
# if desired, we could use the extracted variable as the question indicates
#company = Company.find_by_name(company.require(:company))
Full example in controller
Of course, we could wrap this up in a handy method in our controller:
class UsersController < ApplicationController
before_action :set_user, only: [:create]
def create
# ...
#user.save
end
def set_user
company = params.require(:user).extract!(:company)
#user = User.new(params.require(:user).permit(:name, :age))
#user.company = Company.find_by_name(company.require(:company))
end
end

To be possible to delete you can do a memo:
def parameters
#parameters ||= params.require(:root).permit(:foo, :bar)
end
Now you can do:
parameteres.delete(:bar)
parameters
=> <ActionController::Parameters {"foo" => "foo"} permitted: true>

Related

How to access nested objects in strong params

Hello I'm quite new to rails API. I'm having trouble on how can I access the :guest object from the params. What I want to to do is to create a new record in Booking and Guest. Thank you.
Booking Controller
attr_accessor :guest
def create
booking = Booking.new(reservation_params)
booking.guest.build({booking_id: booking.id, first_name: ?, last_name: ?})
end
def reservation_params
params.require(:booking).permit(:start_date, :end_date :guest => [:first_name, :last_name])
end
POST
{
"start_date": "2021-03-12",
"end_date": "2021-03-16",
"guest": {
"first_name": "John",
"last_name": "Doe",
}
}
1. You're assigning a local variable - not an instance variable.
attr_accessor :guest
def create
booking = Booking.new(reservation_params)
end
Here you might assume that since you declared a setter with attr_accessor that this would set the instance variable #booking so that you can access it from the view? Wrong. When performing assignment you need to explicitly set the recipient unless you want to assign a local variable.
attr_accessor :guest
def create
self.booking = Booking.new(reservation_params)
end
But you could actually just write #booking = Booking.new(reservation_params) since that setter is not actually doing anything of note.
2. Models don't have an id until they are saved.
This line:
booking.guest.build({booking_id: booking.id, first_name: ?, last_name: ?})
Is actually equivilent to:
booking.guest.build(booking_id: nil, first_name: ?, last_name: ?)
One big point of assocations is that the ORM takes care of linking the records for you. Let it do its job. If you're ever assigning an id manually in Rails you're most likely doing it wrong.
3. You're not saving anything to the DB
.new (build is just an alias for new) just instanciates an new model instance. You need to actually save the object for it to have any effect beyond the current request.
How do I fix it?
If you want to use that parameter structure it can be done with a bit of slicing and dicing:
def create
#booking = Booking.new(reservation_params.except(:guest)) do |b|
b.guest.new(reservation_params[:guest])
end
if #booking.save
redirect_to #booking
else
render :new
end
end
The reason you use except(:guest) to remove the guest param is that the setter defined by the assocation expects an instance of guest and not a hash so it will blow up otherwise
Nested attributes
accepts_nested_attributes is the Rails way of passing attibutes through another model. It expects the parameter to be named guest_attributes not guest.
class Booking < ApplicationRecord
belongs_to :guest
accepts_nested_attributes_for :guest
end
If you really need to use the existing params structure you can just alter the parameters in your whitelisting method:
class BookingsController < ApplicationController
# ...
def create
#booking = Booking.new(reservation_params)
if #booking.save
redirect_to #booking
else
render :new
end
end
private
def reservation_params
params.require(:booking)
.permit(:start_date, :end_date, guest: [:first_name, :last_name])
.tap do |p|
# replaces the key :guests with :guest_attributes
p.merge!(guest_attributes: p.delete(:guest))
end
end
end
You're trying to create two associated models as once, you can use:
accepts_nested_attributes_for.
In the booking model add:
# models/booking.rb
accepts_nested_attributes_for :guest
And then in the controller:
def reservation_params
params.require(:boking).permit(:start_date,...,
guest_attributes: [:first_name, :last_name])
end
And then you can just create the booking including the guest like so:
booking = Booking.new(reservation_params)
Find more info here:
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

i'm trying to pass json data to rails controller using post method in rest client

Here i'm trying to save json data to sqlite database using rails controller, but i'm not getting json data to controller parameters
In a specific controller I have the below list of params:
Parameters: {"person"=>"{\"name\":\"akhil\",\"profession\":\"it\",\"address\":\"hyderabad\",\"mobilenum\":67588}"}
Controller
def createPerson
puts "parameters are : "+params[:person].to_s
user_params = ActiveSupport::JSON.decode(params[:person])
puts "parameters name:"+user_params[:name].to_s
#person = Person.new(name: user_params[:name], profession:
user_params[:profession], address: user_params[:address], mobilenum:
user_params[:mobilenum])
#person.save
end
It is showing below error
(no implicit conversion of nil into String)
I'm getting the nil value in user_params[:name].to_s
Could you please help me to solve this
Seems like all you need to do is to create a new Person record after submitting a form. Well, probably you would want to use strong params and make a little refactor, so your controller will look something like this:
class PersonsController < ApplicationController
# you can name your action simply `create`, so you can use resource routing
def create
# most probably you don't need to make person an instance variable
person = Person.new(person_params)
# and here it is a good practice to check if you really saved your person
if person.save
# do something to tell user that the record is saved, e.g.
flash[:success] = 'person has been saved'
else
# show user why your person record is not saved, e.g.
flash[:error] = "person cannot be saved: #{person.errors.full_messages.to_sentence}"
end
end
private
# and here is the method for your permissible parameters
def person_params
params.require(:person).permit(:name, :profession, :address, :mobilenum)
end
end

How to merge a value in a params has in ruby 1.8 and rails 2.x

I have a legacy rails application that has some security issues I am trying to fix.
In particular it is saving from a post using:
#user.update_attributes(params[:user])
I want to make sure that when saving, one of the attributes is explicitly set in the controller. There is a variable:
#company
I want to make sure the user.company_id has the value from #company variable before the call to update_attributes.
You can merge into params at the controller if you want.
user_params = params[:user].merge(:company_id => #company.id)
#user.update_attributes(user_params)
The merge will ensure any company_id in the params gets overwritten
just update the user params along with company_id by trying something like this.. it is always advisable to whitelist params by defining pvt method..
def user_params
params.permit(:user)
end
#user.update_attributes(user_params.to_h.merge!({company_id: #company.id}))
If company_id is not present in params[:user] then you can set comapany_id using before_filter in the controller as:
before_filter: set_company_id
private
def set_company_id
##Assuming you have the #user and #company objects before using
#user.company_id = #company.id
end
so after when you execute:
#user.update_attributes(params[:user])
#user object will have all the desired attributes saved...

creating has_one association error Rails 4

I'm trying to create and order that is associated with an item.
An Order has one item:
class Order < ActiveRecord::Base
has_one :item
end
An Item belongs to an order:
class Item < ActiveRecord::Base
belongs_to :user
end
According to the guide this should work:
build_association(attributes = {})
create_association(attributes = {})
I have this in my controller:
def create
#order = #current_item.build_order(order_params)
#order.save
redirect_to #order
end
And this is the error I'm getting:
undefined method `build_order' for nil:NilClass
I know this has to do with how I've defined current_items but I've tried many different things and all lead to this same error message.
I have this in my application helper:
def current_item
Item.find(params[:id])
end
Can anyone point me in a better direction for how to define this or what I'm doing wrong here. Thanks for your help!
1) You don't have access to a helper method from the controller. You can include the helper class in your controller but it's a really bad practice. You must use helper methods only in the views.
2) You can move current_item method from the helper to the controller. Then there will be another problem. In your create method, you are trying to access instance variable #current_item which is not initialized at the moment, not the method. You can do it this way:
#order = #current_item.build_order(order_params)
to
#order = current_item.build_order(order_params)
Then current_item will return you Item object.
3) I am not sure what are your params, but you can implement it this way:
def create
#order = Order.new(params[:order])
#order.save
redirect_to #order
end
where params[:order] is for example:
{name: "order 1", item_id: 1}
You should change your create to use a method, rather a variable, so modify it as follows:
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
# rest of code
def current_item
Item.find(params[:id])
end
This should help.
Good luck!
The error you're getting is being caused by trying to run Item.find(params[:id]) but not passing it a valid value. It seems that params[:id] is maybe nil? Can you confirm this using a debugger or by temporarily adding raise "Params[:id] is set to #{params[:id]} to the first line of the method, running the code and seeing what it says in the terminal output?
All you need to do make this work is have a parameter value for the item come from the form that is being submitted. Normally rails uses the route/url to populate the value of params[:id]. For example, when the request is GET /items/1, params[:id] is 1.
In this case though, unless you've done some custom routing that you haven't shown in your question, creating a new order would usually be a POST to /orders and since there is no id in the url, params[:id] is nil.
It's up to you to add the item id from the order form. It would make sense that it would be sent with the rest of the order params as item_id, rather than just id, since id is usually used to reference the current object, which is a new order and therefore doesn't get have an id.
You'll need to make sure that item_id is whitelisted in your strong params with the rest of the values in the order_params method (I assume you defined this in the same controller but did not show it in the code), and then the code would look something like this.
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
#note the changes the the argument
def current_item
Item.find(order_params[:item_id])
end
def order_params
params.require(:order).permit(:item_id, :other_values_that_you_send)
end

Rails STI not saving "type" field

I'm trying to set up a single table inheritance for Questions table. I've followed some advices adding a route this way :
resources :vfquestions, :controller => 'questions', :type =>
'Vfquestion'
And the model :
class Vfquestion < Question
end
It works, saving the question in the database, but the type field stays empty.
Here is my controller :
class QuestionsController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
#category = Category.find(params[:category_id])
#questions = #user.questions.where(:type => params[:type])
end
def new
#category = Category.find(params[:category_id])
#question = #category.questions.new
#question.type = params[:type]
end
def show
#user = current_user
#category = #user.categories.find(params[:category_id])
#question = #category.questions.find(params[:id])
end
def create
#user = current_user
#category = Category.find(params[:category_id])
#question = #category.questions.new(question_params)
#question.user_id = current_user.id
#question.save
end
private
def question_params
params.require(:question).permit(:title, :body)
end
end
Am I missing something to save this param ?
As far as I know, type is not saved for the base class. It also can't be overridden via params, as that would mean X.new would potentially yield an instance of a class other than X.
What you need to do is create the correct type on the way in:
#question =
case (params[:question][:type])
when 'Vfquestion'
Vfquestion.new(params[:question])
else
Question.new(params[:question])
end
#category.questions << #question
The relationship is also defined in terms of a singular base class, so all objects built in that scope will default to the base class.
I'm guessing in your Category model you have a line:
has_many :questions
The problem is that this relationship is pointing to the parent Question model; it has no idea you want to create or find any of its subtypes (Remember that Rails operates on convention over configuration; in this case, rails is locating your Question model because of the convention for naming has_many relationships).
One way to solve this is add the appropriate subtypes like so:
has_many :vfquestions
has_many :some_other_question_subtype
And then to create, for example, a new VFQuestion for a particular category, you would simply do:
#question = #category.vfquestions.new(question_params)
Side Note
Part of the problem in your situation is, in your create method, you have no way of distinguishing between a VFquestion, or some other question sub type when you go to create it. You'll have to figure out the best way to handle this for your particular domain, but possibly the simplest way to handle this is to pass a type parameter from the form. So, for example, if you have some kind of radio button that flops between the different question types, make sure it is named appropriately to it is sent when the form is submitted. Then simply check that piece of data in the params and either invoke .vfquestions, or some other question sub type.

Resources