Rails noob ,
I am trying to write a custom validation but I am dependent on parameters that come from the post request. This example is similar to what I have - in this post model I want to validate that a user didn't already post on some topic, but to do that I have to have the topic id from the post request:
class Post < ActiveRecord::Base
...
validate :user_already_posted, on: :create
def user_already_posted
topic=Topic.where(id: params[:topicId]).first
//some code that runs on topic users and makes sure the user hasn't posted there
However , I learned that params isn't global and models can't access it (I last used laravel where this isn't the case , so it's confusing for me).
Are custom validations not suited for what I need ? do I need to look at filters or have the controller run a validating function of it's own?
You could do this:
validates_uniqueness_of :user, scope: :topic_id
May not be exactly what you need, but the point is that you can validate within a scope.
You can also pass an array to validate on multiple parameters.
validates_uniqueness_of :user, scope: [:topic_id, :post_id]
I think you are having a bit of a design problem here. You are creating a post validator, but the validation itself is not about the post instance. I mean, the model validator must evaluate all of the post attributes, using the default validators built-in ActiveRecord or creating custom ones, like you did, acessing the attributes via self.
I believe a class method receiving all your parameters and checking the conditions of the post creation is more clear:
Class method on the model
def self.user_already_posted(topic_id, user_id)
# Do your thing
end
Usage on the controller
can_create = Post.user_already_posted(params[:topic_id], params[:user_id)
Related
I'd not claim myself to be an expert in Rails by any stretch. One of the things that confuses me is Strong Parameters, and I've not found any really straightforward tutorials on it, with the majority of the search results dominated by hits on the rails documentation which, whilst normally accurate, I don't find in any way easy to read and can't be considered a tutorial. The github for strong params also doesn't appear to cover this.
Say I have an entity called "Resource".
class ResourcesController < ApplicationController
...
def create
#resource = Resource.new(resource_params)
if #resource.save
...
respond_with(#resource.level)
else
...
end
end
def update
if #resource.update(resource_params)
...
respond_with(#resource.level)
else
...
end
end
...
def resource_params
params.require(:resource).permit(:name, :url, :description, :level_id)
end
end
Assume I have a scaffolded form which displays the fields for name, url, description and level_id. All of the fields are mandatory. I don't know how to amend the resource_params function to ensure that name, url and level_id are mandatory when updating (or creating) a resource, but that description is optional (but should still be permitted).
I've tried removing description from the require line, and adding it on a separate line as params.permit(:description) but that has not made any difference, the field is still mandatory in the form.
Any assistance on this would be welcomed!
As I said, this is nothing to do with strong parameters. You just need to remove required: true for that field to make it optional.
I think you should think about :on
http://guides.rubyonrails.org/active_record_validations.html#on
validates :email, uniqueness: true, on: :create
It enables validations only on specified action.
Strong parameters are used to ensure that no other data can pass into your object, that no1 can insert not wanted data. For example if you have entity User and it has field admin that if it is true, the user has admin role, it can not be set using the create or update method inside your controller, because some1 can send any kind of parameters to your controller, and you want to filter them (Allow only specific parameters).
This does not mean if you put something inside strong parameters that it is required for create, update or whatever. Strong parameters only are there to filter the data, and allow only certain parts of your entity to be settable through this service or REST. If you send parameters that are not in strong params defined, in your server console/log there will be unpermited parameters: list of the parameters.
I have an action controller to validate params that are coming from view.
def duplicate
#params to be validated
params = params[:group_to_duplicate]
#params have to be validated to avoid method 'find' for some reason
group = Group.find(params[:id])
#validate to avoid this 'if'
if group
group.duplicate params
notice = 'Some message'
else
notice = 'Some other message'
end
redirect_to groups_path, notice: notice
end
How to validate the request parameters coming from view, like laravel 5 enter link description here?
Sometimes you do need to validate request params, and not the model.
API's for example. You often need to validate the params given. Maybe you require a date range for an API that returns stock quotes.
The quote model is valid, but in order to fetch it properly you need to know the range the user needs because you don't allow them to have the entire quote history. Something like that.
Also in the case of a search form. Maybe you have a form that requires the user to type the last name before searching for an employee.
In these cases you can use form or view objects to help with the validation.
Here's quick article on form objects:
https://robots.thoughtbot.com/activemodel-form-objects
I've been looking at this as well. I'm going to give this a shot when I get some free time:
https://github.com/nicolasblanco/rails_param
In most cases, you should not have to validate request parameters in your controller.
The best practice for model form validations (required, unique, length, max, etc. ) is to define validations in the model i.e. /models/group.rb, and use a form library such as simple_form which automatically handles the model validations you've defined.
For example, in your group.rb you'll have:
class Group < ActiveRecord::Base
validates :group, presence: true
end
If you absolutely need to validate in your controller, use Action Controller Filters: http://guides.rubyonrails.org/action_controller_overview.html#filters
References:
http://guides.rubyonrails.org/active_record_validations.html
https://github.com/plataformatec/simple_form
I have two models
class Information < ActiveRecord::Base
belongs_to :study
validates_presence_of :email
end
and
class Study < ActiveRecord::Base
has_many :informations
accepts_nested_attributes_for :informations
end
I show up a form of study which contains few fields for the informations and i want to validate presence of those fields. Only on validation success i wanted to save the study field values as well and i wanted to show errors if the validation fails. How can i do this? Thanks in advance.
You write validations in the models that you require, as normal. So if you need to validate presence of field foo in the Information class you'd just write validates_presence_of :foo in that class. Likewise validations for Study fields just go in the Study class. With nested attributes, when you update a Study instance from a params hash that contains nested attributes, it'll update the Information instance(s) too, running validations in passing. That's what the accepts_nested_attributes_for call is doing - it's giving "permission" for the appropriate bits of a params hash to be used in this way.
You can use reject_if to only reject new nested records should they fail to meet criteria. So I might let someone create a Study and only create one or more nested Information instances associated with that Study if they'd filled in field(s) in the form, but if they left them blank, the nested stuff wouldn't be created and saved (so you don't get pointless blank associated records). The Study would still be saved. For example:
accepts_nested_attributes_for(
:informations,
reject_if: proc() { | attrs | attrs[ 'title' ] .blank? }
)
This and more is covered in the API documentation here:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Beware that nested fields are intended for existing records only. If you were creating a new Study instance in a new/create action with no Information instances associated, you won't see any nested form fields for your Information class at all - when you might be expecting just one, for a blank new item. This can be very confusing if you aren't ready for it! You'll need to manually add a new Information instance to your Study instance in the controller or similar for the 'new' and 'create' actions, e.g. using before_filter :create_blank_object, only: [ :new, :create ], with, say:
def create_blank_object
#study = Study.new
#study.informations << Information.new
end
you can use validates_presence validation available in rails other wise you can write before_create or before_save callback method. write validation logic inside the before_create or before_save callback method.
Check out the API Doc for validates_associated:
Validates whether the associated object or objects are all valid. Works with any kind of association.
If you call a method on the parent object which runs validations (e.g. save), the validation on the associated objects will be called as well.
When you have a rails resource defined rails seems to automatically create a params entry of attributes for that resource. e.g. if my model Lesson has a subject attribute and I post subject=Maths it automatically creates the param[lesson] = { subject: 'Hello' }. The problem I am having is getting nested attributes to appear within this created lesson array.
I'm using mongoid as my backend and have an association on Lesson called activities. The code looks like this:
class Lesson
include Mongoid::Document
field :subject, type: String
embeds_many :activities, class_name: 'LessonActivity' do
def ordered
#target.sort { |x, y| x.display_order <=> y.display_order }
end
def reorder!
#target.each_with_index { |val, index| val.display_order = index }
end
end
accepts_nested_attributes_for :activities
However I can't work out how I access this activities from within params.require(:lesson).permit :activities
I can access it via params.permit(:activities) but that feels a bit messy
I've done some digging and found out what's going on with this.
It all comes from a rails feature, the Param wrapper, details and api. Which configured for json will automatically pass the attributes of the model into a param of the model name (in this case Lesson).
The attributes of the model that will be populated based on how the model responds to the method attribute_names so this gives two routes to achieve the aims of the question.
1 - Instruct my controller to include activities as part of Lesson parameters, e.g. using this method:
class Api::LessonsController < Api::ApiController
wrap_parameters Lesson, include: Lesson.attribute_names << :activities
2 - Update the attiribute_names method for the model to include :activities
I'm still left with a couple of things to resolve, namely the reason associations aren't part of attribute_names on Mongoid and if overriding it to include attribute names is a bad idea.
Basing on the params you provided for your JSON POST request, you will need the following code to whitelist the params you need:
def activities_params
params.require(:activities).permit(:title, :display_order, :content, :time)
end
The params forwarded by your JSON POST request did not have the :activities hash as a value to the :lesson key so whitelisting the params you need is simple like above.
I think you may have answered you question here:
"how I can make it part of lessons key or why I can't. I'm not passing a lesson parameter "
If I read that correctly, you are not passing the lesson param, just a hash of Activities?
That would explain why you can access
params.permit(:activities)
but not
params.require(:lesson).permit :activities
This problem is related to this question: Hashing an IP for saving
I have a model called Post. I have to pass in the IP address during post creation. I was told not to override the initialize(). So I used a factory method as suggested here: how to override new method for a rails model:
#Post.rb model
def self.new_with_ip(ip, attributes={})
self.new(attributes['one_day_id'] = do_some_conversion_on(ip))
end
However this does not get invoked, because Post is nested within a Discussion, and the nested form will not call this factory method. How can I make the form to invoke this instead of the traditional Post.new()?
If this is being passed through as a nested attribute of a form then you would have to override the posts_attributes= method of the Discussion model:
def posts_attributes=(attribute_sets)
attribute_sets.each do |attributes|
Post.new_with_ip(ip_goes_here, attributes)
end
end
Of course you're going to need to modify that a little if you're going to be getting nested posts in an update kind of fashion, as you'll want to update existing posts rather than creating new ones. Sounds like a good exercise in learning :)