Rails model validation - ruby-on-rails

How would I validate a model in the following scenario. We have three models, being account, time log and project. An account has_many projects, and a project belongs_to account.
When a user creates a time log, they are able to select from a list of projects associated with that account, put in some more details, and save the log.
One of our developers has pointed out that it's possible to manipulate the code going back to the controller when a time log is being saved and if you pass the id of a project belonging to another account back to the controller, that project name then becomes visible in a view. In this way you could build a list of other account's projects, which is not cool.
So what I want to achieve is to validate the record being saved to ensure that the project id is actually a project associated with the current_account.
How would I achieve this?
At the moment, this is how I am building the time log
def create
#log = #employee.time_logs.build(params[:employee_time_log])
#log.account_id = current_account.id
if #log.save
flash[:notice] = "Time log sucessfully saved."
redirect_to employee_time_logs_path(#employee)
else
render :form
end
end
and the time log model looks like this
class EmployeeTimeLog < ActiveRecord::Base
#validations
validates :date, presence: true
validates :description, presence: true
#associations
belongs_to :employee
belongs_to :account
belongs_to :company_project
end

You're talking about a case of privilege escalation here.
The Rails Security Guide has some tips about this:
This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and they are not allowed to see that information, they will have access to it anyway. Instead, query the user's access rights, too:
#project = #current_user.projects.find(params[:id])
In your case, you want to allow this:
#log = #employee.time_logs.build(project_id: 'good', …)
and disallow this:
#log = #employee.time_logs.build(project_id: 'bad', …)
All projects belonging to an account are queried like so:
current_account.projects
which can be used for further queries:
current_account.projects.find('good')
#=> returns a record because ID belongs to account
current_account.projects.find('bad')
#=> raises ActiveRecord::RecordNotFound
And so that's your way of ensuring you have the right project_id passed to your controller!
user_supplied_project_id = params[:project_id]
timelog_params = params.merge(project_id: current_account.projects.find(user_supplied_project_id))
#log = #employee.time_logs.build(timelog_params)

Thanks for all the help. This was the solution in the end
user_supplied_project_id = params[:employee_time_log][:company_project_id]
timelog_params = user_supplied_project_id == '' ? params[:employee_time_log].merge(company_project_id: '') : params[:employee_time_log].merge(company_project_id: current_account.company_projects.find(user_supplied_project_id).id)

Related

How to pass the params to two different tables in rails

As a newbie I started to do API POC. I have a situation as explained below:
I have seekerController which has create method.I want that when a Post request makes then few parameters has to go seeker table and few needs to go profile table(This table also have the seekerID column). I want to do this with in Transaction commit. So after reading I started doing below:-
ActiveRecord::Base.transaction do
seeker = Seeker.new(seeker_params)
seeker.save!
params[:seeker_id] = seeker[:id]
seekerprofile = SeekerProfile.new(seekerprofile_params)
seekerprofile.save!
end
render json: {status: 'success', message: 'Request is processed successully', data:seeker},status: :created;
I have below definition:(I have doubt i the below way is correct)
def seeker_params
params.require(:seeker).permit(:username, :alias, :mobile_number, :country_code, :email_address, :description, :status)
end
def seekerprofile_params
params.require(:seeker_profile).permit(:seeker_id, :first_name, :middle_name, :last_name, :date_of_birth, :pincode, :building_name, :address, :email_address, :description, :status)
end
Let me put my question straight forward here:-
I have post body request parameter like below:
{
"username" : "TestName12",
"alias" : "TestAlia12",
#above should go to seeker table
"first_name":"xyz",
"Last_Name":"abc"
#above should go above Seekerprofile table. seekerprofile has seekerid also.
}
My Model is below:-
> class SeekerProfile < ApplicationRecord
>
> belongs_to :seeker end
I have tried what i have posted in start code , but i am getting the error as seekerprofile_params is empty. So I am sure my approach is wrong.
Can anyone please provide the sample code , how to do that? I am java guy , so fresher for ruby.
With the limited information that is given, it seems as though the problem could be related to the seeker_id field being blank in the result of seekerprofile_params. Basically, we're setting params[:seeker_id] as params[:seeker_id] = seeker[:id] after saving Seeker. But while creating params for creating SeekerProfile, we use seekerprofile_params which looks for seeker_id in params[:seeker_profile][:seeker_id] since we use params.require(:seeker_profile) before permitting seeker_id. Since SeekerProfile does not get a seeker_id, it may not get saved depending on how the model is setup.
However, in case you're trying to create both, a Seeker as well as a SeekerProfile, you may want to check out nested attributes in Rails.
Edit after receiving more inputs:
Considering that the API contract cannot change and needs to be maintained, the following approach could be used to create a seeker and a seeker_profile:
1) We could change the model Seeker to accept nested attributes for SeekerProfile as follows:
# app/models/seeker.rb
has_many :seeker_profiles # As mentioned in the question comments
accepts_nested_attributes_for :seeker_profiles
2) The controller code could then be changed as follows:
# app/controllers/seeker_controller.rb
def create
seeker = Seeker.new(creation_params)
seeker.save!
render json: {status: 'success', message: 'Request is processed successully', data:seeker},status: :created
end
private
def creation_params
params.permit(:username, :alias).merge(seeker_profiles_attributes: [seeker_profile_creation_params])
end
def seeker_profile_creation_params
params.permit(:first_name, :last_name)
end
What happens here is basically we allow the seeker model to accept attributes for seeker_profiles during creation. These attributes are accepted by the model using the seeker_profiles_attributes attribute writer. Since the relationship is a has_many relationship, seeker_profiles_attributes accepts an array of objects, where each hash object represents one seeker_profile child to be created.
In the code mentioned above, I've assumed that only one seeker_profile is to be created. In case your API changes and wants to accept multiple profiles during creation, I would leave that upto you to figure out, with the assurance that you could get back in the comments in case you're stuck.
Another thing to note is that the ActiveRecord::Base.transaction block is not required since failure in any of the objects being created would rollback the entire transaction anyway.

rails autosave nested model selected with where/find_by

i have a special case for which i need to know the best practice.
Given a simple has_many association:
class Authentication < ActiveRecord::Base
belongs_to :user
#provider can be :password, :facebook_oauth etc
#code is the encrypted password on provider == :password
end
class User < ActiveRecord::Base
has_many :authentications
#this works
def encrypted_password=(pw)
set = false
self.authentications.each do |auth|
if auth.provider.to_sym == :password
set = true
auth.code = pw
end
end
self.authentications.build(provider: :password, code: pw) unless set
pw
end
#this only when no password-auth exist yet
def encrypted_password=(pw)
self.authentications.find_or_initialize_by(provider: :password).code = pw
end
end
and then
user = User.last
user.password="abcdefg"
user.save
While the first solution works, it loads and iterates over ALL associated Authentication objects. It was a workaround but this is a no-go.
The second solution does not work when it loads an existing Password-Authentication object. The User object does not know about the change on the Authentication object loaded with the find_or_initialize_by method. The change won't be saved...
Is there a way to register the changed Authentication object back to the User object so that it will be autosaved when called user.save?
It seems saving associating object returned with find back to parent object is impossible as of now. Refer to this issue https://github.com/rails/rails/issues/17466.
I had the same issue, and my workaround was, even though this is not you nor I wanted, to use save in the method yourself and make all the saves inside the transaction.
def encrypted_password=(pw)
self.authentications.find_or_initialize_by(provider: :password).update_attribute(code, pw)
end
Is there a way to register the changed Authentication object back to the User object so that it will be autosaved when called user.save?
If your question only consists of needing to know how to save an associated class, you can add this to your class definition:
class User < ActiveRecord::Base
has_many :authentications, autosave: true
end
The Authentication object is already referenced back to the User object via the user_id column that should be on Authentication by way of the belongs_to method. This autosave: true will save the associated object Authentication when the parent object (User) is saved.

Validation fields not working how they should

I'm developing an app that requires the user to enter info into a form. Its has basic validation on some of the fields to check that the're not blank.
The user model has many user_entries
has_many :user_entries, dependent: :destroy
and the user_entry model belongs to user
belongs_to :user
the problem arises with the validation in the user_entry model
validates :name, :address, :email, presence: true
before adding the foreign key user_id to the user_entries table this code worked fine, I could fill out the form with no problem and add the entries to the table. But I need to capture the current user id within the user_entries table so I can trace an entry to a user.
def create
#user_entry = UserEntry.new(params[:user_entry])
#user_entry.add_comp_connections_from_entered_competition(current_entered_competition)
#user = current_user
#user_entry = #user.user_entries.build
#--etc--#
removing the validation lets me do this, but I don't want to remove it. It seems that removing the foreign key lets me validate. But I want both to work, any help would be very much appreciated, thanks
As stated by SteveTurczyn changing
#user = current_user
#user_entry = #user.user_entries.build
to just
#user_entry.user = current_user
makes the code work, like solving so many problems its a case of not over complicating things.
thanks again Steve Turczyn

Create validation based on users 'bet' so they can't bet over their 'balance'?

The user has a balance (user.balance).
The user can post bets on a game (bet.amount).
How could I stop the user from betting more than what is in their balance?
I assume I could create a validation that looks something like this?
def enough_funds?
#bet.bet_amount > self.current_user.balance
flash[:notice] = "You do not have the available funds for this bet"
end
I'm still new to rails, be gentle :)
You're on the right track:
class Bet < ActiveRecord::Base
belongs_to :user
validate :funds_suffiency
def funds_sufficiency
errors.add :bet_amount, "is more than your available balance" if bet_amount < user.balance
end
end
If Bet's :bet_amount is less than the related User's available :balance, the error will be added to :bet_amount attribute, invalidating the model instance.

Rails - Allow a model to associate with existing objects or create a new one

Let's say I have a two models, Event and Person. An event has a coordinator that is a person:
class Event < ActiveRecord::Base
belongs_to :coordinator, :class_name => 'Person', :foreign_key => 'coordinator_id'
accepts_nested_attributes_for :coordinator
end
class Person < ActiveRecord::Base
validates :name, :length => 20
end
In my form, I would like to let the user pick from existing People objects (let's say a list of radio buttons), but also have a text box to create a new Person (with the name entered in the text box).
How would I elegantly implement this? I can figure it out, but it involves a lot of ugly code in the Controller, and is a pain to validate.
I've done something similar in which the radio buttons set person[id] and then I just checked for the id.
So in my controller#create method:
if params[:person][:id]
#person = Person.find(params[:person][:id])
else
#person = Person.new(params[:person])
#Handle saving #person here.
end
You may have to delete the id param in the elseblock if the form sends it even if nothing is selected.
Edit to answer validation question:
In the #Handle saving #person here. is where you'd do what you normally do for creating an object. Like:
if #person.save
flash[:notice] = "User created successfully"
else
render :action => 'new' # (or whatever the action is)
return
end
The validation code on person, will be executed everytime you save a person.
To save bypassing the validator, write #person.save(false).
Hope it integrate the pcg79's answer

Resources