Rails Controller: how do you account for optional and required parameters? - ruby-on-rails

In the tutorials I have seen, create has been implemented like this:
def create
#note = Note.new(note_params)
#note.save
redirect_to #note
end
private
def note_params
params.require(:note).permit(:title, :type, :description, :dueDate)
end
I have read about the fetch method for optional parameters, but how do I mix the two methods to require some parameters and permit others? Would it be like this:
private
def note_params
params.require(:note).permit(:title, :type)
params.fetch(:note, {}).permit(:description, :dueDate)
end
In this case, could I expect to pass 0, 1, or 2 of the fetched params?

You're reading it wrong, I think. In this line
params.require(:note).permit(:title, :type, :description, :dueDate)
The required parameter is :note. And :title, :type and others are simply permitted/allowed to appear under :note. None of them are required by this syntax. If you really need :title to be there, that is best handled by presence validation on your Note model.
class Note
validates_presence_of :title
end
Now, if you don't pass params[:note][:title], #note.save will return false and you can render form with user-friendly errors (highlight missing fields, etc.). You wouldn't be able to do that (as easily) if note_params method raised an exception on missing title attribute. So that is how you handle required record attributes in rails.

Related

Rails multiple params permit calls

I am trying to use multiple permits in a single method similar to the following (psuedocode)
def index
model.create(
params.permit(:b, :c)
)
params.permit(:a)
end
This is my actual code
def create
params.permit(:create_special_categories)
balance_sheet = ::BalanceSheet.create!(
balance_sheet_params.merge(date: Time.zone.now.to_date, entity: #entity)
)
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
def balance_sheet_params
params.permit(
:id,
:entity,
:entity_id,
:date,
:name
)
end
However, I get the following error...
ActionController::UnpermittedParameters:
found unpermitted parameter: :create_special_categories
UPDATE
my solution was to avoid strong parameters all together.
def create
balance_sheet = ::BalanceSheet.new(
date: Time.zone.now.to_date, entity: #entity
)
balance_sheet.name = params[:name]
balance_sheet.save!
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
This line doesn't have any effect, params.permit are not chained or added to a previous permit, you must use the result, that is why it's almost always used in a separate method.
params.permit(:create_special_categories)
What you must do is use what that returns for your following statements
permitted_params = params.permit(:create_special_categories)
Model.create(permitted_params)
...however you really should outsource this to a special method like you already have. You will have to tweak this to your use-case obviously.
def balance_sheet_params
if params[:create_special_categories]
params.permit(:id,
:entity,
:entity_id,
:date,
:name,
:create_special_categories)
else
params.permit(
:id,
:entity,
:entity_id,
:date,
:name)
end
end

Ruby on Rails new() method not storing values

I have a modified copy of https://github.com/talho/openphin/blob/master/app/controllers/admin/invitations_controller.rb
The main code is primarily the same, however our system was upgraded a few months back to Rails 4.x and the invitation system no longer works.
The method with the issue is create. I have tried swapping out:
#invitation = Invitation.new(params[:invitation])
with
#invitation = Invitation.create(invitation_params)
And creating
def invitation_params
params.require(:invitation).permit!
end
However, here is what I have:
invitation_params = {"name":"Test","subject":"test","body":"test","organization_id":"","author_id":24448}
#invitation = {"id":null,"name":null,"body":null,"organization_id":null,"author_id":null,"subject":null,"created_at":null,"updated_at":null,"lock_version":0}
Also, if I use create!, then my output error is:
E, [2015-12-14T13:03:38.664099 #24385] ERROR -- : Validation failed: Author can't be blank (ActiveRecord::RecordInvalid)
I could use any guidance/help on why everything ends up as null.
You call return what leaved the method, before you call save! in the record. Furthermore you might want to read about Strong Parameters. You might want to change your code to:
#invitation = Invitation.new(
params.require(:invitation).permit(
:name, :subject, :body, :organization_id, :author_id
)
)
#invitation.save!
render :json => { :invitation => #invitation }.as_json
return
Please note that you usually do not need to call return in controller method. And when you call save! immediately after new then create! might be an better option:
def create
invitation = Invitation.create!(invitation_params)
render json: { invitation: invitation }.as_json
end
private
def invitation_params
params.require(:invitation).permit(
:name, :subject, :body, :organization_id, :author_id
)
end

Updating Strong Params Only

First of all, I believe there must be some people, who already asked this question before but I don't know how can I google this problem. So, if it is duplicate I am sorry.
I am working on a social media site. I have user model, which I use to register users to the site. It validates, name, email, and password when registering.
I use the same model to make users edit their informations, like username.
This is what I have in my update controller:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if #profile.update_attributes!(settings_profile_params)
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private # user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(:first_name, :last_name, :username, :school, :program, :website, :information)
end
The problem is, I only want to update strong parameters that I defined there. But I am getting an exception because of password validation. I don't know why am I getting this exception. How can I set up system to update the values in strong parameter only.
Thank you.
You can achieve this by changing you password validation. You need to add a condition on password validation.
# Password
validates :password,
:presence => {:message => 'Password cannot be blank'},
:length => {:within => 8..99, :message => 'Password length should be within 8 and 99 characters'}
:if => Proc.new { new_record? || !password.nil? }
By calling update_attributes you are implicitly invoking the same range of validations as an other update and save. You need to update on the specific params you're targeting (e.g. omitting :password).
Here, we can store that list of permitted keys in a variable that is reusable. Then we call update_attribute on each of those keys — doing so within a reduce that gives the same true/false for the switch to edit or display.
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if PERMITTED_KEYS.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
PERMITTED_KEYS = [:first_name, :last_name, :username, :school, :program, :website, :information]
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(PERMITTED_KEYS)
end
Having not used strong_parameters gem before, I think this would be more idiomatic to the use of the gem:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if settings_profile_params.keys.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(
:first_name, :last_name, :username,
:school, :program,
:website, :information
)
end
Though, I still think this is a duplicate question, since it regard how to update model data without all of the defined validation. I've answered in case the update_attributes loop is felt to be a sufficiently unique solution to warrant non-duplication.
Okay, now I found the problem. First of all, #Muntasim figured out a way to solve this problem. But I actually don't need to use this solution, because there is another easy way to fix this.
In this situation, when I let users to update their profiles, rails should not validate my password or any other column in user model, if I don't ask it to. But why was it validating? Because I have validates :password in user model. Instead it has to be validates :digest_password. Because I am using bcrypt.
I don't know why :password was working fine when I register even though I used bcrypt.

Find_by_slug with Mongoid method undefined

I am using Mongoid(3.0.23) and I want to add nicer URL's, I have followed this rails cast but for some reason my site throws an undefined error for the find_by_slug method. I have read about some gems I could use but it seems pointless for such a simple task.
Model
validates :slug, :uniqueness => true
before_validation :generate_url
def generate_url
self.slug ||= self.title.parameterize if slug.blank?
end
def to_param
slug
end
field :slug
View
<% #events.each do |e| %>
<%= link_to e.title, event_path(e) %>
<% end %>
Controller
def show
#event = Event.find_by_slug!(params[:id])
end
Maybe try:
Event.find_by(slug: params[:id])
Also, not sure if it's necessary but you could specify the type:
field :slug, type: String
Mongoid defines the attribute finder, but not the bang version.
Event.find_by_slug(params[:id])
# => valid
Event.find_by_slug!(params[:id])
# => not defined
In any case, given the way ActiveModel is taking and according to best practices, it's better for you define all the public API of your model.
class Event
def self.find_by_slug!(slug)
where(slug: slug).first || raise(Mongoid::Errors::DocumentNotFound, self, slug: slug)
end
end
You can also re-use find_by_slug, but as I said, because ActiveRecord is deprecating find_by_attribute, I prefer to write the code directly.

How to remove a field from params[:something]

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>

Resources