Well, we all know the new amazing feature of last ActiveRecord::Store class.
But how can we do validations safely? Here an example:
class User
has_many :posts
end
class Post
belongs_to :user
store :composed_attribute, accessors: [:attribute_1, :attribute_2]
validates :attribute_1, presence: true # This works great!
validates :attribute_2, presence: true, uniqueness: {scope: :user_id, message: "must be unique."} # This fails!
end
The first validation works great, but the second fails with and undestandable undefined method 'text?' for nil:NilClass.
In database everything is stored in yaml format. but we cannot be sure about the order, so which is the best way to perform such validation?
Disclaimer
I know that, maybe, in this case store the attributes together is not a great idea, mostly for performance issues, but it's an interesting topic anyway.
Related
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]}")
Probably an easy one.
Consider this simple rails model
class Something < ActiveRecord::Base
attr_accessible :URL, :name
validate :name, presence: true
validate :URL, presence: true
It is a complete ressource, in routes:
resources :something
created with :
rails generate scaffold something name:string URL:string
The line:
validate :URL, presence: true
keep throwing error:
uninitialized constant Something::URL
I really don't know what to do
Thanks
It most likely doesn't like the fact that URL is uppercase. (It thinks it's a class name).
It's against the convention and RoR is very big on conventions.
I'm working on a Rails 3.0.x application (actually it's Hobo 1.3.x but that's not material to this question). Among the models, there are GraphPanes, GraphLabels, and LabelSets. A GraphPane can have GraphLabels and LabelSets. GraphLabels can belong to GraphPanes or LabelSets, but not both. So if a GraphLabel belongs to a LabelSet, I'd like to keep it from being associated to a GraphPane.
I am trying to enforce that with this code in the GraphPane model:
has_many :graph_labels, :conditions => 'label_set_id = NULL'
However, I'm still able to associate GraphLabels with not-null label_set_id with GraphPanes. Why? How can I stop this?
This question is superficially similar, but my relationship isn't polymorphic, so the nominal solution there doesn't help me.
The functionality of :conditions on has_many is to filter the results that are passed back via the graph_labels, not to protect objects from being added to the association.
If you add a graph_label with no label_set_id, the association will build, but if you then ask for graph_pane.graph_labels, it will not return that non-condition-matching graph_label.
The has_many/belongs_to relationship is saved on the belongs_to model, graph_label, and so the parent/has_many/graph_pane does not stop the graph_label from writing whatever it wants to its graph_pane_id attribute. This delegation of responsibility is correct, although frustrating, I agree.
Now, as for how to stop this, I'm not sure. It sounds like you need some sort of validation on the graph_label object, something along the lines of not allowing a graph_pane_id to be set on a graph_label if that graph_label's label_set_id is nil. Since the has_many/belongs_to relationship is saved on the graph_label, you should write the validation on the graph_label. That way, the graph_label will not be able to be saved with a new graph_panel_id unless it fulfills the condition.
Thoughts? Questions?
Reference:
has_many
Alternate Solution
I've reread your question and I think want you want here is a polymorphic association.
def GraphPane < ActiveRecord::Base
has_many :label_sets
has_many :graph_labels, as: :parent
end
def LabelSet < ActiveRecord::Base
belongs_to :graph_pane
has_many :graph_labels, as: :parent
end
def GraphLabel < ActiveRecord::Base
belongs_to :parent, polymorphic: true
end
That way, a GraphLabel can only have a single parent, which is what your “spec” above requires. Is there any reason not to implement the relations in this way?
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.
What is the best way to add a check for accepting terms of use in a rails app?
I can't seem to get validates_acceptance_of working quite right. I added a bool to my user model (was that necessary?). And then have a checkbox that returns either true/false.
I feel like I'm just making a silly little mistake. Any ideas?
In your model,
validates_acceptance_of :terms
If you're using attr_accessible in your model then make sure you also add,
attr_accessible :terms
In your view,
<%= form_for #user do |f| %>
...
<%= f.check_box :terms %>
...
<% end %>
There is no need for an extra column in the users table unless you plan on denying access to users who have not accepted the terms of service, which won't exist since they can't complete registration in the first place.
This is a working Rails 4 solution:
Terms of service doesn't need to be a column in the database
Form
= f.check_box :terms_of_service
models/user.rb
validates :terms_of_service, acceptance: true
And most important, devise will sanitize your parameters and terms of service will be removed from the submitted params. So:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:full_name,
:email, :password, :password_confirmation, :terms_of_service)
end
end
end
This is a working solution for Rails-6.1 (I18n) + Devise-4.8.0 + SimpleForm. No need to add a column in the "users" table in DB.
View
<% label_str = t('read_html', mylink: link_to(t('terms'), '/a/b/c')) %>
<%= f.input :terms_of_service, label: label_str, as: :boolean, checked: false %>
Or, if you use Indo-European languages only, you can make it a little more simple, like:
label_str = (t('agree_html')+link_to(t('terms'), '/a/b/c')+'.').html_safe
/app/models/user.rb
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, on: :create
validates_presence_of :terms_of_service, on: :create
/app/controllers/application_controller.rb
Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up] << :terms_of_service
# see /vendor/bundle/ruby/*/gems/devise-*/lib/devise/parameter_sanitizer.rb
Explanation
In the User model, on: create guarantees it is read only in creation. If you need to reevaluate the condition in updating, too, specify it accordingly, like on: %i(create update).
In the User model, I add validates_presence_of to play safe. The reason is, validates_acceptance_of will not be executed when the parameter terms_of_service is nil, in which case validates_presence_of will catch it and set an error. Admittedly, if the data are always submitted via the web-interface you have built AND your implementation is working perfectly, the value should be always either true or false and never be nil. So, validates_presence_of should not be necessary in this sense. It does no harm, though (except you'd need to be a little careful in manual user creation, bypassing the web-interface, such as from the Console).
The last one is neccesary for use with Devise for the same reason as in the answer by #vladCovaliov; that is, to prevent Devise from sanitizing your custom parameter, which is not a column in the database table. The one-liner in the example above can be stated in any files as long as you are sure it is read at the run-time and after Devise Ruby code. application_controller.rb is one of the sure places (though I guess there is a better-fitting place). Make sure the sentence is put out of the class ApplicationController block.