I have a Rails app that lets a user construct a database query by filling out an extensive form. I wondered the best practice for checking form parameters in Rails. Previously, I have had my results method (the one to which the form submits) do the following:
if params[:name] && !params[:name].blank?
#name = params[:name]
else
flash[:error] = 'You must give a name'
redirect_to :action => 'index'
return
end
But for several form fields, seeing this repeated for each one got tiresome. I couldn't just stick them all in some loop to check for each field, because the fields are set up differently:
a single key: params[:name]
a key and a sub-key: params[:image][:font_size]
only expect some form fields to be filled out if another field was set
Etc. This was also repetitive, because I was setting flash[:error] for each missing/invalid parameter, and redirecting to the same URL for each one. I switched to using a before_filter that checks for all necessary form parameters and only returns true if everything's okay. Then the my results method continues, and variables are just assigned flat-out, with no checking involved:
#name = params[:name]
In my validate_form method, I have sections of code like the following:
if (
params[:analysis_type][:to_s] == 'development' ||
params[:results_to_generate].include?('graph')
)
{:graph_type => :to_s, :graph_width => :to_s,
:theme => :to_s}.each do |key, sub_key|
unless params[key] && params[key][sub_key]
flash[:error] = "Cannot leave '#{Inflector.humanize(key)}' blank"
redirect_to(url)
return false
end
end
end
I was just wondering if I'm going about this in the best way, or if I'm missing something obvious when it comes to parameter validation. I worry this is still not the most efficient technique, because I have several blocks where I assign a value to flash[:error], then redirect to the same URL, then return false.
Edit to clarify: The reason I don't have this validation in model(s) currently is for two reasons:
I'm not trying to gather data from the user in order to create or update a row in the database. None of the data the user submits is saved after they log out. It's all used right when they submit it to search the database and generate some stuff.
The query form takes in data pertaining to several models, and it takes in other data that doesn't pertain to a model at all. E.g. graph type and theme as shown above do not connect to any model, they just convey information about how the user wants to display his results.
Edit to show improved technique: I make use of application-specific exceptions now, thanks to Jamis Buck's Raising the Right Exception article. For example:
def results
if params[:name] && !params[:name].blank?
#name = params[:name]
else
raise MyApp::MissingFieldError
end
if params[:age] && !params[:age].blank? && params[:age].numeric?
#age = params[:age].to_i
else
raise MyApp::MissingFieldError
end
rescue MyApp::MissingFieldError => err
flash[:error] = "Invalid form submission: #{err.clean_message}"
redirect_to :action => 'index'
end
You could try active_form (http://github.com/cs/active_form/tree/master/lib/active_form.rb) - just ActiveRecord minus the database stuff. This way you can use all of AR's validation stuff and treat your form like you would any other model.
class MyForm < ActiveForm
validates_presence_of :name
validates_presence_of :graph_size, :if => # ...blah blah
end
form = MyForm.new(params[:form])
form.validate
form.errors
Looks like you are doing the validation in the controller, try putting it in the model, it's better suited to that sort of thing.
If you were to tackle the problem again today, you could create a model for the query parameter set and use Rails' built in validations, Rails 3 makes this a lot easier with ActiveModel::Validations see this post.
Model
class Person
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name
attr_accessor :email
validates_presence_of :name,:message => "Please Provide User Name"
validates_presence_of :email,:message => "Please Provide Email"
end
Note that you don't necessarily need to save/persist the model to validate.
Controller
#person.name= params["name"]
#person.email= params["email"]
#person.valid?
One you called .valid? method on the model, the errors will be populated inside #person object. Hence,
View
<%if #person.errors.any? %>
<%#person.errors.messages.each do|msg| %>
<div class="alert alert-danger">
<%=msg[0][1]%>
</div>
<%end%>
<%end%>
Related
This is more going into rails theory. I want to compare strings submitted by a user to strings in a populated model. Is this possible? Even if it is possible would it be better to create a user in putted model and compare the strings from there?
Is there any documentation on how to achieve this
My names are kind of wonky, I adapted this from a microposts tutorial to try and be a dna(user input) --> rna (mrna db) sequencer (which i understand is very bad form)
users/new.html.erb(renders)/_form.hrml.erb
<%= form_for :user do |t| %>
<%= t.fields_for 'inputdna[]', [] do |p| %>
dna <%= p.text_field :dna %>
<% end %>
<%= t.submit "Submit" %>
<% end %>
user model
class User < ActiveRecord::Base
dna_options = 'ttt, ttc, tta'.split(',')
#validates :dna_options, presence: true
# returns true|false
def dna_valid?(user_input)
user.input.each
return false if unless dna_options.include? user_input
end
return true
end
controllers/users_controller.rb
class UsersController < ApplicationController
# GET /users
# GET /users.json
def index
#users = User.new # not touched, however the convention would normally be User.new
end
def new
User.create(full: params['user']['full'], dna: params['user']['inputdna']['dna'], mrna: params['user']['mrna']) if User.dna_valid? params['user']['inputdna']['dna']
end
end
seed.rb for users model
User.create(full: "phenylalanine", dna: "ttt", mrna: "uuu")
User.create(full: "phenylalanine", dna: "ttc", mrna: 'uuc')
User.create(full: "leucine", dna: 'tta', mrna: 'uua')
It is possible to compare strings, there are few methods to do so, and the difficulty around user inputs that may prove a challenge.
Assuming you had a model such as:
Class SomeStrings
##some_string = 'String Answer'
end
You could compare this to user input from some params like so:
params['user_input'] == SomeStrings.some_string
This will return a boolean (true|false) for use in if/case statements.
Now, to ensure that the inputs match regardless of case you have two simple options:
params['user_input'].downcase == SomeStrings.some_string.downcase
or
params['user_input'].casecmp(SomeStrings.some_string)
http://ruby-doc.org/core-1.9.3/String.html#method-i-casecmp
The latter approach with casecmp is alot faster than downcasing both string parts.
Now in this example I used a class variable ##some_string, however you could apply the same logic to any model with variables/methods that return a string. This model could be an activerecord, or other ORM record, or just a class.
Shout if you want anymore input.
<>
If you wish to pre-populate this model with something on a user by user basis you will need create some level of persistence. This could be in your code 'hard coded', a database or the session. For example, if you want to compare some users input against one of a list of 5 words stored in the session you could do this:
session['words_to_compare_to'] = Words.generate_five_random_words unless session['words_to_compare_to']
you could create a method on your model that creates words, and this would assign them into the session. This means each user would get some words to compare to which are random per user. This could be your method:
class Words
##words_list = 'help, go, north, fred, mimi, the, and, elf'.split(",")
def generate_five_random_words
words_to_return = []
5.times{ words_to_return << ##words_list.sample }
return words_to_return
end
end
Then to compare the input you receive to see if it is within the 5 random words in your controller you could do this:
session['words_to_compare_to'].include? params['user_input'].downcase
We are using downcase here as all of our words list are lower case. This returns a boolean true|false if the user input is found within the array of 5 words. I hope this provides something that you can reuse.
* Update following code addition to question *
I am going to assume that the user fills out the text field and submits it to a route /user/create. I am assuming the purpose is to check that the 3 letter dna that the user submitted is within an acceptable dna list.
So to start, lets add the acceptable dna combinations to the user model:
Class User
dna_options = 'ttt, ttc, tta'.split(',')
# returns true|false
def self.dna_valid?(user_input)
dna_options.include? user_input
end
end
Now, depending on which ORM you are using, you can also use validators for this: http://guides.rubyonrails.org/active_record_validations.html
To use the above within your controller you could do this:
class UsersController < ApplicationController
def index
#users = Array.new # not touched, however the convention would normally be User.new
end
def create
User.create(full: params['user']['full'], dna: params['user']['inputdna']['dna'], mrna: params['user']['mrna']) if User.dna_valid? params['user']['inputdna']['dna']
end
end
Now, I have made lots of assumptions here, as your code is missing the same entities as your model, however I hope you see what can be done. If params['user']['inputdna'] returns an array, you could change the code to:
if user.dna_valid? params['user']['inputdna']
and change the boolean operator within the model to:
# returns true|false
# expects user_input to be an array
def self.dna_valid?(user_input)
user.input.each do |dna|
return false if unless dna_options.include? dna
end
return true
end
This returns true if all the entities are found within the dna_options, or false if one of them does not
If you're looking for only comparing and finding exact matches, then this is what I used.
all_users = params[:user][:dna].map do |u|
User.find_by(name: u)
end
Here you're getting the response from the form submission in the "params[:user]", then it is comparing that response to the User table using the "User.find_by(name: u)" and assigning matches to "all_users" through the map loop.
The problem you might encounter here is if the form submission does not find an entry in the model, which will then return a "nil". This simple logic does not deal with "nil" returns.
questions_controller.rb
def index
#questions = Question.all(app_params)
end
private
def app_params
params.require(:questions).permit(:question, :answer)
end
end
question.rb
class Question < ActiveRecord::Base
end
I am completely new to ruby-on-rails. I was following a guide and it said I should take care of some "loopholes" or "security issues" and it used attr_accessible, but on Rails 4, they suggest strong parameters, so now I'm trying to use them. I'm confused on how to define the :questions params, because I'm currently getting an error saying that :questions param is not found.
:questions is pretty much something that I will define myself as the web developer.
So for example, I will define questions = "How are you?", "What is your name?". I'm basically starting very simply. I want questions that I have created to be displayed on my webpage. Ultimately, I plan to make a website what is basically a list of questions and, with answer options. After the user clicks "submit" I want to store the information into my database.
Am I supposed to even be requiring this as a param? I'm completely lost..
Do you have a dump of the params we could look at? They are shown when your app encounters an error, and typically shows you the params array which rails will pass through
Strong Params In Rails 4
Strong Params allow you to allow certain parameters for use in the controller, protecting against any malicious assignment client-side. They replaced attr_accessible in Rails 4.0
Strong Params is only for user-submitted content, as it's designed to protect the params hash. To that end, it's mostly used with the create and find functions:
class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
params.require
The params.require function works by taking this params hash:
params{:question => {:question => "1", :answer => "5"}}
That's why people asked what your params hash looks like, because the require function can only work if the :question hash is present.
Possible Solutions For You
Question.all(app_params)
Regardless of what you're trying to achieve, don't use all. The where function is better for receiving an array of data based on certain values. I believe all is depreciated anyway.
def index
#questions = Question.where("value = ?", variable)
end
What data is being passed?
I will define questions = "How are you?", "What is your name?"
This is okay, but typically in rails, you'd call data by using an ID in the database. If you're defining these questions in a form, you'd use the strong params system; but you'd need a form to submit the data to
Further Additions
The rails way is to keep all your data in a database, and use the application to manipulate that data, either by showing it, or allowing people to input more.
The "params" variables are basically there to help the rails controllers & models accept & process data from end users, and consequently allow you to keep the system growing. Instead of having to write custom code to accommodate all sorts of different data, the params give you a rigid structure to work with. Here is a good explaination of how MVC (and params) works for you: How does an MVC system work?
I think you're getting confused with how your app should work
Your "questions" should be stored in a questions table / model, and can be accessed by calling their ID's with the find function. This code would be like this:
#app/controllers/questions_controller.rb
def show
#question = Question.find(params[:id])
end
If you want to add new questions, you'll be best to add them to the questions table, like this:
#app/controllers/questions_controller.rb
def new
#question = Question.new
end
def create
#question = Question.new(question_params)
#question.save
end
private
def question_params
params.require(:question).permit(:question)
end
#app/views/questions/new.html.erb
<%= form_for #question do |f| %>
<%= f.text_field :question %>
<% end %>
This will give you a central store of your questions, which you'll then be able to access when you need them, either with a helper or with your ".all" call :)
Give it a shot with question (singular):
params.require(:question).permit(:text, :answer)
Assuming question is your model and text (which I made up) is the wording of the question.
I have two fields in a form I would like to validate the presence of, before sending. The problem though is that the controller's model doesn't have these fields in the database, so I tried making virtual attributes. I'm not quite sure how to get it to work though.
I tried doing this in the Model, called "Find_number"
class FindNumber < ActiveRecord::Base
attr_accessor :name
attr_accessor :original_number
validates :name, presence: true
validates :original_number, presence: true
end
and the following in the create action of the Find_numbers controller
def create
#user = current_user
client = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
search_params = {}
%w[in_postal_code near_number contains].each do |p|
search_params[p] = params[p] unless params[p].nil? || params[p].empty?
end
local_numbers = client.account.available_phone_numbers.get('US').local
#numbers = local_numbers.list(search_params)
if :name.valid? && :original_number.errors.any?
unless #numbers.empty?
render 'find_numbers/show'
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
end
When I enter info though, I get the error
undefined method `valid?' for :name:Symbol
I'm probably calling the :name and :original_number attributes incorrectly, and the double if then statements look very newbish :P.
What would I need to replace if :name.valid? && :original_number.errors.any? , to make sure that it validates? Or is there a lot more I'm missing?
I think you are confusing the boundary between 'controller' and 'model'. The controller doesn't know anything about the validations inside of the model that you've written. The fact that the controller is called FindNumbersController and the model is called FindNumber doesn't really mean anything in terms of shared functionality.
You would need to explicitly create an instance of the model with the passed in params, and let the model perform the validation on the instance
find_number = FindNumber.new(params.slice(:name, :original_number))
Then you can ask whether the instance as a whole is valid
find_number.valid?
or whether a specific field has any error messages
find_number.errors[:field].any?
So, :name.valid? becomes find_number.errors[:name].empty? and :original_number.errors.any? becomes find_number.errors[:original_number].any?
I have the following in my user.rb model:
INVALID_EMAILS = %w(gmail.com hotmail.com)
validates_format_of :email, :without => /#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/, :message => "That email domain won't do.", :on => :create
For various reasons, I want to be able to use this logic in my controller to check an email's input before it is user.created, which is when the above normall runs.
How can I turn the above into a method that I can call in controllers other than user? Possible?
And if is called and returned false I then want to do errors.add so I can let the user know why?
Thanks
Trying:
def validate_email_domain(emailAddy)
INVALID_EMAILS = %w(gmail.com googlemail.com yahoo.com ymail.com rocketmail.com hotmail.com facebook.com)
reg = Regexp.new '/#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/'
self.errors.add('rox', 'Hey, Ruby rox. You have to say it !') unless reg.match attribute
end
Update:
..
Rails.logger.info validate_email_domain(email)
...
def valid_email_domain(emailAddy)
reg = Regexp.new '/#{User::INVALID_EMAILS.map{|a| Regexp.quote(a)}.join("|")}/'
return true if emailAddy.scan(reg).size == 0
end
You cannot assign a constant inside a method, because that would make it "dynamic constant assignment". Instead, define this constant in your model class and then reference it in your controller by using User::INVALID_EMAILS
Okay, if I understand you.
You want to do something like below:
u = User.new
u.email = "jsmith#gmail.com"
if !u.valid?
puts u.errors.to_xml
//do something
return
end
What you do with those errors is going to come down to how you want those reported back, usually I just shoot them back as xml into a flash[:error], which is the normal default behavior if you're doing scaffolds. The puts is there so you can see how to access the errors.
Additional
As a rule try to avoid duplicating validation logic. Rails provides everything you need for validating without creating different methods in different places to accomplish the same thing.
I am using Ruby on Rails 3 and I would like to know some behaviours of this code:
#user.send :attributes=, #attrib, false
That is from here.
If I have a form that return these parameters to my controller:
params[:name]
params[:surname]
params[:email]
and in the controller I use
#user.send( # Avoiding 'attr_accessible'
:attributes=, {
:name => params[:name],
:surname => params[:surname] },
false )
#user.save
it should save only 'name' and 'surname' attributes for the #user ActiveRecord. I tryed that, and it works as expected. But...
1. is it possible that a malicious user can set the email value in someway on the saving process (also if the email attribute is not considered in the "send" statement)?
2. is it right the following statement?
Calling attributes= with false doesn't
update anything yet, it just sets the
attribute values while ignoring any
attr_accessible whitelist.
So you can just call save afterwards,
which returns the boolean value you're
looking for.
Your code will definitely not allow a malicious user to set the email. I would recommend using the following code though, since it performs the same thing and is easier to read:
#user.name = params[:name]
#user.surname = params[:surname]
#user.save
Also, passing true as the second parameter to attributes= allows you to protect your attributes using attr_protected and attr_accessible. You can view the documentation here. That means your statement is correct: passing false as the second attribute ignores your mass-assignment protected attributes.