Here is the code generated by rails:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
flash[:notice] = 'User was successfully updated.'
format.html { redirect_to(#user) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
But I don't want user to update the whole user, assume that my user have fname, lname and gender, instead of remove the gender from the view, I want to restrict that the update method ONLY accept fname and lname only, if he/she want to update the gender, I won't allow him/her to do so. How can I restrict the user to do so? thank you.
or add a custom #user.update_only() method, which makes it also easier to reuse in different contexts...
class User
def update_only(attrs = {}, *limit_to)
update_attributes(attrs.delete_if { |k,v| !limit_to.include?(k.to_sym) })
end
end
Then just do something along the lines of
#user.update_only(params[:user], :fname, :lname)
There are two methods in ActiveRecord that come in handy in cases like these, attr_protected and attr_accessible.
You use them like this:
class MyModel < ActiveRecord::Base
attr_accessible :fname, :lname #Allow mass-assignment
attr_protected :secret #Do not allow mass-assignment
end
model = MyModel.new(:fname => "Firstname", :lname => "Lastname", :secret => "haha")
puts model.fname # "Firstname"
puts model.lname # "Lastname"
puts model.secret = nil # Can not be set through mass-assignment
model.secret = "mysecret" # May only be assigned like this
puts model.secret # "mysecret"
However, if you only need this functionality at one place, then Salil's solution will work just as well.
One thing to note is that you should use attr_acessible to whitelist attributes that are OK to mass-assign, and make every other attribute protected. By doing so, you hinder mean people for updating data they are not supposed to touch.
See the docs for more info.
Use Hash parameters of the update_attributes
#user = User.find(params[:id])
#user.update_attributes(:fname=>params[:user][:fname], :lname=>params[:user][:lname])
You can delete unwanted attributes from the param[:user] Hash:
# ...
attributes = params[:user]
gender = attributes.delete :gender
raise SomeError unless gender.blank?
if #user.update_attributes(attributes)
# ...
end
# ...
This code removes :gender from the Hash and checks if it is filled in. If so, an exception is raised. Of course you could give a nice warning or silently ignore the fact that the gender was filled in.
Related
I need to validate a field before the create method
In my _form.html.erb I have two models, one is the owner model, and the other is a model I create to have other arguments, I need to validate those arguments before getting in the create method, I can use an if, but it is not the best practice to do it.
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
city = city_params()
#customer.city_id = City.find_by(name: city["name"]).id
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
I need to validate the the city name, because the customer owner must have the city_id, and the _form requests the name (string), so I need to find the city but previously I need to validate the city name has a value and it exists,
How can I validate this in the model ?
If I were you, I would start out by keeping all of this logic in the controller and use a filter to find the city:
class CustomersController < ApplicationController
before_action :find_city, only: [:create, :update]
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
#customer.city_id = #city.try(:id) # This returns `nil` if the city was not found
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
private
def find_city
#city = City.find_by(name: params[:city][:name]) # No need for strong parameters for this
end
end
Then make sure you're validating the presence of city_id in your Customer class:
class Customer < ActiveRecord::Base
validates :city_id, presence: true
end
Later on, if you find that you need this logic to be extracted from the controller, then consider looking at creating a service object or a form object. Because this is a simple case (only 2 classes are involved), I would hold off on creating those constructs for now though. The controller layer is sufficient enough to handle this simple logic.
Why not move the logic directly into the model? I can tell you from experience that you do not want to mess your model up with tons of logic involving other model classes. Customer should not really know much about City in my opinion.
before_validate
You could use the before_validate callback in your model:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
attr_accessor :city_name
before_validate :set_city
private
def set_city
city_id = City.find_by(name: city_name).id
end
end
--
Custom Validation Method
I think the bottom line is you'll be best using a custom validation method for this. You basically want to return the user to the form with an error saying "City not found" or similar; which is entirely within the remit of a custom validation method:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
validate :check_city_id
private
def check_city_id
errors.add(:city_id, "City doesn't exist") unless City.try city_id
end
end
--
System
This kind of issue can be handled by simply giving the user options to select the id at input; rather than selecting by name:
#app/views/customers/new.html.erb
<%= form_for #customer do |f| %>
<%= f.select :city_id, City.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
I think your method of giving the user the ability to pick a city name, and then validating in the backend is very inefficient; whilst giving the user a rigid set of options to select a buyer by city is far more robust
We have something called callbacks http://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html ..using this we can trigger our required validations in the model.
You can create your own logic of validation like example
before_create :method_name
def method_name
your logic.....example: validates :city_name ..
end
I got this model:
class Post
def rating_by(ip_address, user = nil)
if user
ratings.where("ratings.user_id = ?", user.id).first
else
ratings.where("ratings.ip_address = ?", ip_address).first
end
end
end
As you might notice, I allow ratings by both users and visitors.
I'd like to output posts as json with an additional user_rating attribute.
This is my current controller:
#posts = Post.trending(10)
respond_to do |format|
format.html
format.json { render json: #posts.to_json }
end
Of course this won't show it, but I'd like to know if there's a possibility of using to_json's :methods option and specify parameters as well, something like:
#posts.to_json(extra: {user_rating: "rating_by(#{request.remote_ip}, #{#current_user.id})"})
So that I end up with something like:
[{ id: 54, title: "Foo", user_rating: 8 }]
Other suggestions are very welcome!
I commented on your question, but I thought I would add some detail.
From Active Model Serializers' page :
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :full_name
def full_name
"#{object.first_name} #{object.last_name}"
end
end
So you could define a user_rating parameter in your serializer.
Moreover, Active Model Serializers allows the use of a meta
render json: #posts, meta: {total: 10}
So you could simply computer user_rating and send it with meta.
Sorry if the title is a little confusing. I have a form for an Item with the field name. There's a text field where the user can input a name and submit it. But if the user doesn't type in anything and hits submit, Rails gives me a param not found: item error, and I'm not sure who to get around this.
items_controller.rb
def new
#item = Item.new()
respond_to do |format|
format.html
format.json { render json: #item }
end
end
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to items_path }
format.json { render json: #item, status: :created, location: #item }
else
format.html { render action: 'new', :notice => "Input a name." }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
private
def item_params
params.require(:item).permit(:name)
end
app/views/items/new.html.haml
= form_for #item do |f|
= f.label :name
= f.text_field :name
= f.submit "Submit"
The params.require(:item) part is what is causing the error. What the convention for handling the error when params[:item] isn't present?
It's late for an answer but i'll still write it for someone else. As stated in rails guides you need to use fetch instead of require in strong parameters, by using fetch you can provide a default value if nothing is passed as input. Something like:
params.fetch(:resource, {})
Update:
Scaffolded rails4 app:
https://github.com/szines/item_17751377
It works if a user keep name field empty when create a new item...
Looks, it works without problem...
Development.log shows that parameters would be the following if user keep a field empty:
"item"=>{"name"=>""}
There is always something in the hash...
As Mike Li already mentioned in a comment, something wrong... because shouldn't be empty this params[:item]...
You can check if something nil, with .nil? , in this case params[:item].nil? will be true if it is nil. Or you can use .present? as sytycs already wrote.
Previous answer:
If you have situation when :item is empty, you should just use params[:item] without require.
def item_params
params[:item].permit(:name)
end
More information about require in strong_parameters.rb source code:
# Ensures that a parameter is present. If it's present, returns
# the parameter at the given +key+, otherwise raises an
# <tt>ActionController::ParameterMissing</tt> error.
#
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
# # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
# # => ActionController::ParameterMissing: param not found: person
#
# ActionController::Parameters.new(person: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
self[key].presence || raise(ParameterMissing.new(key))
end
I personally have not switched to strong parameters so I'm not sure how one should handle something like:
params.require(:item).permit(:name)
but you can always check for item presence with something like:
if params[:item].present?
…
end
In my application, only users with the administrator role may create new users. In the new user form, I have a select box for each of the available roles that may be assigned to new users.
I am hoping to use the after_create callback method to assign the role to the user. How can I access the selected value of the select box in the after_create method?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end
In the user model I have:
after_create :assign_roles
def assign_roles
self.has_role! 'owner', self
# self.has_role! params[:role]
end
I receive an error because the model doesn't know what role is.
You could use attr_accessor to create a virtual attribute and set that attribute as part of your create action.
The short answer is, no. You cannot pass any arguments to after_create.
However what your trying to do is a pretty common and there are other ways around it.
Most of them involve assigning the relationship before the object in question is created, and let ActiveRecord take care of everything.
The easiest way to accomplish that depends on the relationship between Roles and Users. If is a one to many (each user has one role) then have your users belong to a role and sent role_id through the form.
<%= f.collection_select :role_id, Role.all, :id, :name %>
If there is a many to many relationship between users and roles you achieve the same by assigning to #user.role_ids
<%= f.collection_select :role_ids, Role,all, :id, :name, {}, :multiple => :true %>
The controller in either case looks like
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end
I have a order object, which have a create method like this :
def create
#order = Order.new(params[:order])
# #order.status_id = "1"
respond_to do |format|
if #order.save
flash[:notice] = 'Order was successfully created.'
format.html { redirect_to(#order) }
format.xml { render :xml => #order, :status => :created, :location => #order }
else
format.html { render :action => "new" }
format.xml { render :xml => #order.errors, :status => :unprocessable_entity }
end
end
end
I want to set the #order status_id to "1", so I have the
#order.status_id = "1"
But this code is not work, after I uncomment it, it still can't save "1" in the status_id in db, but other attribute can successfully store.
order.rb
class Order < ActiveRecord::Base
has_many :order_items
belongs_to :status
end
You should be able to assign a string value to a relationship key and it will be converted as required. The params always come in as strings, so obviously this needs to occur for anything to be working.
This is the kind of situation where a robust unit test would help narrow down the scope of the problem. It may be one of:
There is an attr_accessor defined for status_id that is blocking the assignment.
There is a status_id= method defined which is blocking the assignment.
The status_id is being reset during a before_validation or before_save call.
You'd want to create a test routine that verifies this works:
def test_status_assignment
order = Order.new
order.status_id = '1'
order.save
assert_equal 1, order.status_id
end
You could have attr_accessible in your model blocking you from assigning to that field. It does this by using a whitelist of approved fields.
Change #order.status_id = "1" to #order.status_id = 1
Ruby may be reading "1" as a string (which it is supposed to do) instead of an integer (what you are probably looking for). When you remove the quotes ruby will interpret it as an integer. If you must leave it as a string, try something like my_string.to_i or "123".to_i.
If this doesn't work, please post whatever error message you are receiving.
This
#order.status_id = "1"
should be
#order.status_id = 1
Or maybe
#order.status = Status.find_by_id(1)
if you have relationships set up nicely