In my Ruby on Rails application I am trying to add in validations that will ensure the user has entered a value in a text box. In my system I have a table called Account which stores users' email account information, when they go onto the views/accounts/_form.html.erb page to add a new email account I want to validate the presence of a port number. I can do this through the following code:
validates :port, presence: true
This successfully ensures that users enter their port number, but when a user signs up through the views/users/_new.html.erb page they have to enter only an email address (e.g example#example.com) and the users_controller will then create a record in the Account table for this email address. My problem is that on the views/accounts/_form.html.erb page the port number is required but on the views/users/_new.html.erb it is not.
Is there a way of validating that the user enters the port number if they are on the views/accounts/_form.html.erb page or invoking the create method in the accounts_controller?
I am aware that I could do this through the HTML required validation like so: <%= f.text_field :port, :required => true %> but I need to add in further validation as well as presence, so this is not suitable.
You can create an attr_accessor field that determines if the validation should occur...
class Account < ActiveRecord:Base
attr_accessor :port_needs_validation
validates :port, presence: true, if: -> {port_needs_validation}
Then just set the accessor in your create method...
def create
#account = Account.new
#account.assign_attributes(account_params)
#account.port_needs_validation = true
if #account.save
...
Extract that part of the logic into a form object, check out the legendary 2012 blog entry from CodeClimate. Things have changed since then, the author uses Virtus to build form objects, more popular & up-to-date gems these days are:
reform
dry-rb
active type
but really you can make anything behave like an ActiveModel object
if it's a one-off thing, just do what Steve said in the other answer but that is a sure way to hell, safe-hate and divorce (at least from personal experience) in any slightly teeny weeny bigger project (i.e. you mean to spend some hours more working on it, it's not like you just finished everything and want to go home).
Actually, just use form classes everywhere and avoid model validations & other callbacks at all. You don't want things sending account activation mails or validating your password complexity when you're writing tests and just need a "post" that belongs to "user".
My own favorite personal fuckup due to model callbacks is sending 240.000 "your account has been upgraded/downgraded" emails because of an innocent spelling change update in an account_type attribute migration just because account_type_changed? was true.
So.. Form classes for ever, model callbacks never.
I would not recommend you have model aware of views. In #SteveTurczyn 's solution, an abstract field is introduced into model to identified the which page it come from, which is an good solution.
As from Ruby on Rail MVC, both View and Model talk to the controller, another solution will be have controller handle validation of params before passing the value to create account.
Related
I have a scenario where I am unsure of whether a particular function should be considered validation or authorization. I can code it either way.
Users can "like" articles.
When a user creates a new "like" I need to ensure the user has not already liked the article. The front end will limit the functionality however I want backed end safeguards.
Should the process of ensuring a user has not already liked the article be considered validation or authorization?
Further to comments received:
If auth determines if the option is available to the user, or not & validation determines if the user selection is valid then...
Auth will make the option to click "like" available even when then user has previously "liked" and therefore it will inevitably fail validation.
This thinking results in an invalid option being presented to the user.
Is ensuring the user can only delete/edit their own "likes" auth or validation? The previous logic implies it should be validation as the user is either authorised to add/update or destroy within the model or not and ensuring their actions are valid is the role of validation However it would be illogical to present the option to delete another user's like only to reject upon failed validation.
This is validation. I don't know your model architecture, but if you have a Like model, you could validate like this:
class Like < ActiveRecord::Base
belongs_to :user_id
belongs_to :article_id
validates :article_id, uniqueness: { scope: :user_id }
end
You should also make sure that a unique constraint is present at the DB level, to avoid a potential race condition.
This sounds more like validation. You have to check in your model that this article was liked by this user or not. If it is, then this like is invalid and he can't like it now. Otherwise, it will pass the validation and the user will be able to like this article.
Authorization should come, when some user can like some set of articles, but not all, in those situation, In my honest opinion.
a. should use rails validation to make sure he/she can like once not more then that.
b.authorization is to restrict user from hitting like.
authorization would be: is this user allowed to perform this action; validation: will this action succeed. given that user is allowed to 'like', ensuring he can do it only once is a validation problem. to solve it put unique constraint on db level (user_id, article_id).
There is so much written about the security threat of attr_accessible that I am beginning to wonder if I should even have any attributes in it. Here is the issue. I have a Message model which has the following:
attr_accessible :body,:sender_id,:recipient_id
I do not have the update or edit action in my messages_controller. With the new and create action I am able to create a new message and send it to a recipient. Only users who have logged in and meet certain conditions can message each other. I do that with the help of a before_filter and the conditions work fine. The message is stored and can be viewed by the sender and the recipient. Perfect!
The question I have is that since :body,:sender_id,:recipient_id are included in attr_accessible, can a malicious user somehow change the :body,:sender_id,:recipient_id of the original message? Should I just add these attributes to attr_readonly as well so they cannot be modified once saved?
This question has been haunting me for practically all my models.
can a malicious user somehow change the :body,:sender_id,:recipient_id
of the original message?
This would depend on other things rather than attr_accesible. attr_accesible will only filter which fields are allowed to be updated using mass assignment. Since you say you don't have any update action, then no, there is now way a user can edit a message since you always create a new Message through you create action.
But there is something you need to care about. What is sender_id? If you do have users in your app and they send messages to each others, then sender_id should not be an accessible field, since this will allow users to send messages on behalf of other users. You probably want to keep that field off the attr_accessible list and do something like this:
m = Message.new params[:message] # body and recipient_id
m.sender_id = current_user.id # this is not mass assignment
m.save
.....
Well, it depends on how your are creating your model's instance. If you use:
FooModel.create(params[:foo])
then yes, your are not secure because a logged in user may pass additional parameters to the request even if you don't provide explicitly form fields for those attributes.
So, for your case, anyone posting to your "create" action with sender_id, recipient_id (values in the request) will be able to change them unless you take care about this assignments in your action.
In Ruby on Rails, it's very easy to update a model from an HTML form. Usually you can just create a form_for with the model, and the fields in there will be updated when the user hits the submit button.
Say though that a malicious user wants to update their 'salary' without going through the proper channels. couldn't they just inject a field by the name of 'salary' when updating their email address (for example) and set their pay to basically be whatever they want? how do i specify which fields can be modified and which can't to prevent this?
Seeing things like
#user.update_attributes(params[:user])
seems scary. They could update anything. I understand the use of attr_accessible, but that's only relevant for mass updates, isn't it?
You can restrict what fields can be mass assigned using:
attr_accessible :name, :address # no :salary
I am using Devise for my authentication. If a hashed_password isn't set, Rails/Devise's validations will require a password to be set, as well as the password_confirmation.
When I invite new users, I obviously don't want to set their password, so when I create the invitation in my system, it fails because user.password is blank.
I can set a temporary hashed_password on the user, but when they enter their own password, the validation checks for :password and :password_confirmation will not happen because hashed_password is set, which is a real problem.
Is there any way to tell Rails that I want to run all the validations except for the ones associated with :password?
I know Rails has :if conditions, which might fix my problem, but Devise declares the :password validation on my behalf, so that essentially is hidden.
How can I get the desired result here?, hopefully in a way that is not a hack.
My current hypothetical solution that is somewhat messy: The only thing I can think of is to create a new Invitation model that is not the User model, and use the Invitation model for the form. When the invitation is submitted I can validate that Invitation and copy over all the values to the new User model. I can save that User without any validations at all.
That's the best solution I dreamed up.
It seems like my solution will be a lot more work than saying something simple like:
user.save(validations => {:except => :password})
EDIT: I have found one part of the solution, but I am still having problems. In our user model, we can override a Devise method to prevent the validation of the password for invitations with this bit of code:
#protected
def password_required?
!is_invited && super
end
The is_invited attribute is just a column I added to the users table/model.
However, there is one gotcha here. When a user accepts an invitation and they arrive to the form where they need to set their password/password_confirmation, valid? will always return true.
This one has me deeply perplexed. I don't see how requires_password? and valid? can be true at the same time. If it requires the password, it should do a validation check and cause the validations to fail.
I'm starting to hate Devise - or just the idea of using gems to build parts of your application in a blackbox. I think the real solution probably is to rip out Devise and just do it all from scratch. That way your app has total control of how all of this works :(
I recently started using this great devise add-on: devise_invitable
It's commonly used so users (or any model) can invite other users to join.
But I adapt it for manually (via an admin panel) invite new potential users to my app.
Hope this helps!
I would like to add an invite_code requirement for users to sign up. Ie. in addition to requiring them to specify an email/password combo, I want an additional field :invite_code. This is a temporary fix so that non-wanted users cannot login during a given alpha period.
I'm confused since Devise doesn't add controllers. I'm sort of familiar with the concept of virtual attributes, and it strikes me that I could add a :invite_code to the model, and then just hard code a step now where it says invite code must equal 12345 or whatever for now.
Does this make sense with devise authentication? And how do I go approaching this from a proper rails restful approach?
Thank you very much.
1) A virtual attribute usually needs a setter in addition to a getter.
Easiest way is to add
attr_accessor :invite_code
attr_accessible :invite_code # allow invite_code to be set via mass-assignment
# See comment by James, below.
to the User model
2) I presume that Devise wants the User model to validate. So you could stop the validation by adding
validates_each :invite_code, :on => :create do |record, attr, value|
record.errors.add attr, "Please enter correct invite code" unless
value && value == "12345"
end
NOTE: added :on => :create since the invite_code is only needed for creating the new user, not for updating.
Try this: http://github.com/scambra/devise_invitable
It adds support to devise for sending invitations by email (it requires to be authenticated) and accept the invitation setting the password.
It works with Devise >= 4.0 If you want to use devise 3.0.x, you must use 1.2.1 or lower If you want to use devise 3.1.x, you must use 1.3.2 or lower If you want to use devise >= 3.2, you must use 1.6.1 or lower...
According to the docs, invitable gives you control over who gets to invites others. People cannot distribute invites if there is a "0" setting for invitation_limit.
From the docs:
invitation_limit: The number of invitations users can send. The
default value of nil means users can send as many invites as they
want, there is no limit for any user, invitation_limit column is not
used. A setting of 0 means they can't send invitations. A setting n >
0 means they can send n invitations. You can change invitation_limit
column for some users so they can send more or less invitations, even
with global invitation_limit = 0.