I need to know how to require nested params in a Ruby on Rails API. I have my method set up for the param whitelisting as below:
def user_params
params.require(:user).permit(:email, :password, :profile => [:name, :birthdate, :addy])
end
However, that makes profile a permitted param, not a required one. I want profile to be required. It's allowed to have no other nested params (essentially everything nil), but must still be required. How do I accomplish this?
Most likely the "required" portion is going to come from your actual model. So, in this case, go to your user.rb file and add the following.
validates :profile, presence: true
Is that what you're talking about? Usually, you're going to want to allow params in the controller and perform validations and stuff in the actual model file.
If you want to make multiple items required in the controller you can add them like so:
def user_params
params.require(:user).permit(...)
params.require(:profile).permit(...)
end
Rails strong parameters documentation in github
Related
I am trying to break up the registration process of my app into nice bite-size chunks.
So I'm posting forms via Ajax and just trying to update some of the models attributes that were available at that step of that process, basically meaning that they won't be valid at each save point.
As a result, I have been using update_attribute which works fine. However, one of my attributes is a has_many association and I'm struggling to get this working.
I have Channelmodel with has_many :channel_tags, and also accepts_nested_attributes_for :channel_tags. Saving and updating work fine when I use the update method but I cannot get it working with update_attribute or update_attributes.
As far as I can tell, I need to use update_attributes. I wanted to do something like:
#channel.update_attributes(channel_tags_attributes: params[:channel][:channel_tags_attributes])
But this doesn't create the new channel_tags. I have also tried with:
#channel.update_attributes(tag_params)
and:
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
But again, it just doesn't seem to do anything.
When checking from the console, it appears that all of it's happening because that it's loading the Channel for the database and then the category.
Am I doing something wrong, or is there a better way of doing this?
Try to change the name to permitted params method:-
def channel_params
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
end
and user this method in update attributes:-
def update
#channel = Channel.find(params[:id])
if #channel.update_attributes(channel_params)
# add your code here
end
end
As you are trying to do it with nested attributes in rails 4 then your code should look like,
In tag model
has_many :channel_tags
accepts_nested_attributes_for :channel_tags, allow_destroy: true
Controller should look like
def update
#tag = Tag.find(params[:id])
puts "==== #{tag_params.inspect} ===="
puts "==== #{tag_params[:channel_tags].inspect} ===="
if #tag.update!(tag_params)
redirect_path
end
end
private
def tag_params
params.require(:tag).permit(:name ,channel_tags_attributes: [:id, :channel_id, :tag_id, :_destroy])
end
While updating attributes please check the server logs as I have inspect the params which you try to update for the tag attribs.
.update_attributes will only when you want particular attributes. .update will use HASH in params which we are defining as strong params.
I have a set of custom fields attached to a devise model called Entrant.
I have two forms, one for registration form (three fields) and one which sits in the account area (12 fields). Most of the custom fields area required but only within the form the sits in the account area.
How do I achieve this?
I am using rails 4.2 and ruby 2.1
You can simply specify validations on actions, that is:
validates :name, presence: true, on: :create # which won't validate presence of name on update action
If you ask where to put your custom fields, then generate devise's views and update corresponding ones with these fields.
There are several ways! You could do conditional validations, for instance
class Entrant < ActiveRecord::Base
validate :foo, if: :account_area?
def account_area?
!new_record? # Assumes that Entrant that has already been saved
# is in the account area
end
end
However, it sounds like your needs are advanced enough that you should consider making a Form Object
A form object is an object that accepts parameters, performs validations on that data, then saves a model instance.
class AccountForm
include ActiveModel::Model
include Virtus # Provides AR like attribute functionality and mass assignment
def initialize(entrant)
#entrant = entrant
end
attribute :foo, String
validates :foo, presence: true # This is only used on the account page, so no need to mess with conditional logic
def save
if valid?
persist!
true
else
false
end
end
def persist!
#entrant.update_attributes(foo: self.foo)
end
end
This is just a great example of how non-rails-specific object oriented programming can make your life easier and your app more maintainable. Make a class like above, stick it in app/forms and restart your server. Then in your controller, you'll just pass it the model
class EntrantController < ApplicationController
def update
#form = Form.new(Entrant.find(params[:id]))
#form.attributes = params[:entrant]
if #form.save
redirect_to some_path
else
render "edit"
end
end
end
By default devise only asks for a combination of email/password, you can add other fields by adding a sanitizer (see there -> Devise how to add a addtional field to the create User form?).
If you want to add other fileds to validate, you should create a secondary Entrant controller and add a specific callback to your model.
Typically:
after_update :validate_entrant_form, if: :property_changed?
I hope this will help you.
validates :name, presence: true, if: :condition_holds?
def condition_holds?
# some code here that evaluates to a boolean
end
Maybe this way help you.
Add attribute in devise model : say attr_accessor :validate_certain. In your controller action, devise model instance say #user have to update like this #user.validate_certain = true. and change your appropriate validation conditions in devise model
validates :name, presence: true, if: :validate_certain_changed?
def validate_certain_changed?
validate_certain.present?
end
When I have to do something like this I like to think of it as it validates if something in in the field but you can also take a nil value
Entrant.validates_presence_of(:foo, :allow_nil => true)
I also have this concern when using devise on customer with forms on separate pages updating different set of customer fields
I believe most of the solution works but I was looking for the simplest, easiest and foolproof way to implement the solution
Thus came this.
validates :phone, :country, :postal_code, :street_address, presence: true, allow_nil: true
The allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form. If you want more protection, you can use extra para like :on => :update
Is it possible to skip validations with a dynamic find/create by method?
For example with regular save I can do something like:
p = Post.new
p.title = nil
p.body = nil
p.save(:validate => false)
Would love to do the same with find_or_create_by_title.
It dosnt look possible with the code in Rails right now however you may have better luck being a little more verbose in how you write the code. You can use find_or_initialize_by_ which creates a new object but does not save it. You can then call save with your custom options, also in the documentation they have a neat demonstration that is hard to find so I will include it below:
# No 'Winter' tag exists
winter = Tag.find_or_initialize_by_name("Winter")
winter.new_record? # true
Good luck and let me know if you need more pointers in the right direction.
For some cases, find_or_initialize_by_ will not be useful and need to skip validations with find_or_create_by.
For this, you can use below alternative flow and method of ROR:
Update your model like this:
class Post < ActiveRecord::Base
attr_accessor :skip_validation
belongs_to :user
validates_presence_of :title, unless: :skip_validation
end
You can use it now like this:
Post.where(user_id: self.id).first_or_create!(skip_validation: true)
I have used first_or_create instead of find_or_create_by here. You can pass more column names and values with this, and your validation will not be worked with this.
You can continue without any changes for strong parameters end and no need to permit this 'skip_validation' so it will work with validations while adding entries.
Using this, you can use it with and without validations by passing a parameter.
Currently skipping validation DOES work with find_or_create_by.
For example, running:
Contact.find_or_create_by(email: "hello#corsego.com).save(validate: false)
will skip a validation like:
validates :name, :email, presence: true, uniqueness: true
I'm using the to_json method on my model object that I created by doing something like:
user = User.find(1)
When I do user.to_json, a lot of attributes are missing, including user.id from the encoded JSON string. It appears that all of the attributes that I've added as attr_accessible from the User model are there, but none of the others. Perhaps that is what to_json is doing, but I think that adding id to attr_accessible is a no go.
What is the right way of solving this problem?
UPDATE
This looks to be a specific issue with Devise. If I comment out the following from user.rb, everything works as expected:
devise :rememberable, :trackable, :token_authenticatable, :omniauthable
I haven't checked but I believe Devise does that for you; it includes only certain attributes via attr_accessible.
In any case the right way to solve this is to override the as_json method like so:
def as_json(options = nil)
{
my_attr: my_attr,
etc: etc
}
end
It's a simple hash and it's a really powerful method to generate JSON in AR, without messing with the to_json method.
By default Devise overrides the serializable_hash method to expose only accessible attributes (so things like the encrypted_password doesn't get serialized by default on APIs).
You could try to override this method and add the auth_token to the hash, something like this:
def serializable_hash(options = nil)
super(options).merge("auth_token" => auth_token)
end
Devise indeed filters the attributes for you, as mentioned by kain.
Nevertheless, I'd rather just append exactly what I need to instead of overriding Devise's logic.
Instead, I'd rather do
def as_json(options={})
json_res = super options
json_res['locked_at'] = self.locked_at
json_res['confirmed_at'] = self.confirmed_at
end
or whatever other attributes your user might have that you want to pass
If you came here looking for Rails 4, like I did, here's some information that will help.
As Alan David Garcia said above, Devise overrides serializable_hash. To force an override, you could do the following, for example, to return all attributes except password_digest when calling Model#as_json.
def as_json(options = {})
options[:force_except] ||= [:password_digest]
super(options)
end
You can specify any desired model attributes to exclude in options[:force_except] instead of just :password_digest.
include something like this in your model class:
attr_accessible :id, :email, :password, :password_confirmation, :remember_me
Initially the id wasn't included in json but after I added it to attr_accessible it worked!!
Let's say I have a form_tag in a view gathering a view user input . With this input that I get through the params hash, I want to verify some of htis information such as email phone name.
Can I just verify in the controller or is this a bad thing? I don't plan to save the thing I get to a db or anything, it's just getting put into an email and sent off, but I want to verify these things before it's sent.
Thank you.
EDIT:
Found this http://www.viddler.com/explore/rails3/videos/6/
class Test
include ActiveRecord::Validations
validates_presence_of :name, :email
attr_accessor :name, :email
end
You can use the model for whatever you need that is related to the object and don't need to save it. Keeping stuff like this in the model is desirable in order to keep controller tidy. Say you have a user model:
#controller
#user.new params[:user]
#user.check_valid
#user.save # is optional
#user.rb
def check_valid
!email.blank? and !phone.blank?
end