Accessing params in validations - ruby-on-rails-3

I would like to show some extra info in error messages resulting from a failed validation. For example suppose I have a class Book with the following validation
validates :name, presence: true, uniqueness: true
When someone tries to insert a book by the same name the following error message is returned
{"name":["has already been taken"]}
Instead I wanna show
{"name":["Book 'Great Expectaions' has already been taken at id:7"]}
Right now to make this happen I have to remove the uniqueness validation that I mentioned above and do the following
validate do |book|
existing_book = Book.find_by_name(book.name)
if existing_book
book.errors.add(:name, "#{existing_book.name} already exists at id: #{existing_book.id}")
end
end
Is there a way to get custom error messages like above without writing a custom uniqueness validation? I was thinking something along the lines of
validates :name, presence: true, uniqueness: {message: "#{self.name} already exists at id: #{Book.find_by_name(self.name).id}"
But this does not seem to work as self.name returns 'Book'. Is there a way to access the passed parameters in this context?

You'll have to do this as a custom validation. I would do it like so:
validate :name_is_unique
private
def name_is_unique
errors.add(:name, "#{other_book.name} already exists at id: #{other_book.id}") if other_book = Book.find_by_name(name)
end
The issue isn't really that you can't include the current model attributes in your validation, its that there's no 'one-liner' way to include another model. The good news is, that's what the validate method is for.
If it bothers you to have this in your model, just write a custom validator so it can be re-used application-wide.

Related

Extra validations on url before an object is created/saved to prevent SQL injection?

Hopefully this isn't an invalid question, but could someone please explain to me what happens when rails creates and saves an object to the database or updates one? I've put several validations in place, but I'm not sure if I've missed something as there doesn't seem to be too much information on how rails secures its models under the hood.
In this code the user is supplying some data(supposed to be a url), I check with a REGEX to see if it is a url. I'm wondering if I need to do any additional SQL protection techniques for something as complicated as a url?
class ListLink < ActiveRecord::Base
belongs_to :list
default_scope -> {order('created_at DESC')}
#the REGEX urls are matched against
VALID_URL_REGEX = /\A(http:\/\/|https:\/\/|www|)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z/i
validates :link_url, presence: true,
format:{with: VALID_URL_REGEX, message: "Please enter a valid url."}
validates :list_id, presence: true
#if is a valid url, ping embedly for more information on it
before_save :embedly #:set_link_info
#not sure what checks rails does on attributes created after validation
#any suggestions on how I can make these safer would be appreciated!
before_create :title, presence: true, length:{minimum: 4, maximum: 200}
before_create :image_url, presence: true
private
def embedly
embedly_api = Embedly::API.new :key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
:user_agent => 'Mozilla/5.0 (compatible; mytestapp/1.0; my#email.com)'
url = link_url.dup
obj = embedly_api.extract :url => url
#extract and save a title and image element to the database
#are these validated by Rails too?
self.title = obj[0].title
self.image_url = obj[0]["images"][0]["url"]
end
end
Thanks for any help!
No extra care required; validates is enough.
Using ActiveRecord you can be sure the input data is properly escaped.
User.last.update_attributes(first_name: 'DROP TABLE users;')
# => true
User.last.first_name
# => "DROP TABLE users;"
You seems to misunderstand the purpose of before_create. It is a callback that's executed right before the record is added into the database. Its purpose is not to validate the object, but to execute custom code, as in the case with :embedly. You want to change before_create to validates here. The latter is called every time before the object is saved on both create and update actions. If you have
validates :title, presence: true, length:{minimum: 4, maximum: 200}
and your object has a too short title, ActiveRecord won't allow you to save it.
As addition to #shock_one answer:
ActiveRecord will call ActiveRecord::Base.connection.connection.quoute on every property, which will properly escape the values against SQL Injections. Same happens if you use hash queries or question mark placeholders in you queries.
e.g
User.where(name: params[:name])
User.where("name=?", params[:name]).
However if you write your own concatenated sql queries you have to use qoute on user input
c = ActiveRecord::Base.connection
User.where("name=#{c.quote params[:name]}")

How to capture user submitted attribute values when ActiveRecord validation fails?

Is there an elegant way to call a method when a specific ActiveRecord validation fails? I'm envisioning something like...
validates :email_address, :uniqueness => true, :when_fail => :call_method
I have a roundabout way of calling the method when the validation fails involving code in my controller, but I'm looking for something more straight-forward within the model. I can't find any relevant examples online.
EDITED out other details to focus on the question.
What about this in model?
Model < ActiveRecord::Base
after_validation do
self.my_method unless self.errors.empty?
end
end
For specific validation you can use http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-5B-5D.

show errors on two form fields after errors on validating uniqueness with `scope`

I have a model that has
attr_accessible :name, :activity
validates :name, uniqueness: { scope: :activity }
It works and it doesn't allow the creation of duplicate entries. But with simple_form it only shows the error on the :name field. I'd like it to have errors on both fields saying that this 'name' and 'activity' combination has already been taken.
I'm thinking I need to create a custom validation method, but I'm hoping there's a more elegant solution that I've overlooked so far.
Is there a way to show errors on both these fields?
You can add another validation on :activity so that it will be marked as duplicate as well:
validates :activity, uniqueness: { scope: :name }
I'm not sure that's the most elegant solution but it will spare you the custom validation method.
I ended up doing
validates :name, uniqueness: { scope: :activity, message: 'This name and activity combination has already been taken.' }
I haven't decided if I'm going to have it validate both and put the message on both fields yet, but that opposite for the :activity field would be the same.

Where can I find the cause of this error message? (rails 3.2.8 nested form)

My New Contract Form use to work ... it still needs some refinement ... but the current version use to work, but now I am getting this message:
3 errors prohibited this codeline from being saved:
There were problems with the following fields:
Contract can't be blank
Client can't be blank
Code can't be blank
I do not have any fields named Contract, Client, or Code ... those are my models.
Here is the params which I raised in the codelines_controller:
{"contract_attributes"=>{"authnum"=>"900700", "st_date"=>"2012-09-03",
"end_date"=>"2012-12-31"}, "client_attributes"=>{"f_name"=>"Esme", "mi"=>"J",
"l_name"=>"Inneed", "birth_date"=>"1986-03-18", "address1"=>"62 Southy View",
"address2"=>"", "city"=>"Fromm", "zip_code"=>"55803", "state"=>"WI",
"medicare_num"=>"3008769788", "medicaid_num"=>"765894567", "member_num"=>"6709875-3",
"soc_sec_care_mgr"=>"Caring Manager", "sscm_ph"=>"1-444-444-4444",
"nurse_care_mgr"=>"Caring Nurse", "ncm_ph"=>"1-555-555-5555", "emer_contact"=>"Always
Here", "ec_ph"=>"1-666-666-6666", "pri_care_phy"=>"The One",
"pcp_ph"=>"1-777-777-7777"}, "code_attributes"=>{"code_name"=>"S-5463",
"status"=>"Active", "description"=>"Transition from schl to work"},
"units_alloc"=>"100.00"}
as you can see all of the fields that need data have data and are not 'blank'
I did contract out to see if I could get a professional to help me design a button to add extra attributes to the form, but I had to cancel the contract because they were not communicating. So part of this process required that I make them collaborators to github and heroku. Maybe they had to change something to work on the project?
Can someone give me some guidance on how to resolve this issue?
Thanks.
look in app/models/theModelThatIsUsed.rb and if you find something like:
validates :contract, :presence => true
validates :client, :presence => true
validates :code, :presence => true
delete that lines

Programmatic way of checking what validations failed in Rails

Is there a way to retrieve failed validations without checking the error message?
If I have a model with validates :name, :presence => true, :uniqueness => true, how can I check if determine what validation failed(was it uniqueness or was it presence?) without doing stuff like:
if error_message == "can't be blank"
# handle presence validation
elsif error_message = "has already been taken"
# handle uniqueness validation
end
There's a relatively new method that let you do just that, it's not documented anywhere as far as I know and I just stumbled on it while reading the source code, it's the #added? method:
person.errors.added? :name, :blank
Here's the original pull request: https://github.com/rails/rails/pull/3369
ActiveModel::Errors is nothing more than a dumb hash, mapping attributes names to human-readable error messages. The validations (eg. the presence one) directly add their messages to the errors object without specifying where they came from.
In short, there doesn't seem to be an official way of doing this.
You can Haz all your errors in the errors method. Try this on an saved unvalid record :
record.errors.map {|a| "#{a.first} => #{a.last}"}