Rails 4 - Finding related user - ruby-on-rails

Task: Showing the profile of an employee straight away after his login.
Issue:
class WelcomeController < ApplicationController
def index
#employee = Employee.find_by_email(params[#current_user.email])
end
end
I tried to code in many ways to associate the email of the current user with his respective details from the employees table and the farthest that I could get was it:
I am sure that I am writing something wrong in this line inside the index thing, but I am researching and all things that I found and tried did not get the employee related to the current user.

Try with this code:
class WelcomeController < ApplicationController
def index
#employee = Employee.where(email: current_user.email).first
end
end
When using Devise, the current user is an instance variable, so you don't need to prefix it with #.
If you are going to have a lot of users, is a good practice to create an index in your database for the email column.

I like kjmagic13's answer. Use the
#current_user.id
It pulls all the info associated with the user from the database
Mori's answer is also good.

The following SQL line in your logs corresponds to the Employee.find_by_email call:
SELECT "employees".* FROM "employees" WHERE "employees"."email" IS NULL LIMIT 1
As Mori pointed out, this means you're finding the employee with a nil email, which means that params[#current_user.email] is nil. Since you have no parameters, there's no need to refer to the params hash regardless. You should refer just to the #current_user.email:
Employee.find_by_email #current_user.email

As Mori's answer states, you probably didn't intent do use #current_user.email as a hash key into params. I think you're trying to look up the employee record for the current user by email (not by an email submitted as a parameter), like so (also avoiding deprecated find_by_* helpers):
#employee = Employee.find_by(email: #current_user.email)
I don't think you want to try to do Employee.find(#current_user.id) - that's just going to look up the Employee whose id matches the current_user's id - unless Employee and User use the same table that's not going to be meaningful

Why not just find by the ID? find_by_* are old.
#employee = Employee.find(#current_user.id)

Related

Ruby on Rails - Setting different names for url

I am completely new to Rails and I have a database that links to a certain page depending on the user's search but it will always give me the id.
For example if a user searches, I will get, "localhost:3000/fruit/1" instead of "localhost:3000/fruit/apple". Does anyone know how to switch the url from an id to name?
You need to define a 'to_param' method in the model you are generating a link for, e.g.
class Fruit < ActiveRecord:Base
def to_param
name
end
end
Then in your controller you need to change the find for the show action to find by the attribute you are using in your 'to_param' method, e.g.
#fruit = Fruit.find_by(name: params[:id])
See http://api.rubyonrails.org/classes/ActiveRecord/Integration.html#method-i-to_param for additional details

Possible to Change Rails Routing Convention?

I'm wondering if it's possible to edit the default Rails routing convention to fetch a specific record based on a field that is not the ID?
For instance, instead of retrieving a specific record based on ID, with the verb/url combination:
GET /users/:id
Retrieve a specific record based on username, with the verb/url combination:
GET /users/:username
I don't see why this would be a problem theoretically, as long as usernames were required to be unique, but I'm having trouble understanding how to implement it based on the Rails Routing Guide.
I have gathered that I will need to add a line to my routes.rb file, to define a singular resource, just prior to:
resources :users
However, I'm having trouble understanding the syntax to accomplish this. Any help in understanding this would be greatly appreciated.
Yes it is possible and they are called Non Restful Routes in the rails documentation
A trivial example is doing the below in your routes.rb
get ':users/:show/:username', controller: "users", action: "show"
and in your UsersController you have a show action that looks like this:
def show
if params[:id].present?
#user = User.find(params[:id])
elsif params[:username].present?
#user = User.find_by(username: params[:username])
end
end
This way you support showing by id and username, if you want do disable support for either of them, modify the if clause as you wish
I think you are looking to change the to_param method like so:
class User < ActiveRecord::Base
def to_param
"#{id} #{name}".parameterize
end
end
This would give the url as: /user/id-name. If you want to get rid of the id before the name it gets a little more complicated. If you were just to remove it, it will more than likely break since ActiveRecord needs the id first for finds.
To get around this I would suggest using FriendlyId gem: https://github.com/norman/friendly_id
There is also a RailsCast showing how to use Friendly_id but its pretty straight forward.
The routes does not care if it is an ID or username.
It is really how you find it in the controller.
Just in the user show controller:
def show
#user = User.find_by_username params[:id]
end

Rails scope model data by user_id

I have a Rails application where a user longs in and I have the user_id in the session. The next step is to create a scope for all model data shown to the user where data.user_id = session[:user_id].
I know I can do the following in each of my controllers
Controller.find_all_by_user_id(session[:user_id])
Yet to me it seems there is probably a better solution. I found the possibility to add a scope to the model, yet the session is not known here and MVC pattern wise it is probably not a good idea to have it there. Is there a solution to apply such a user_id restriction to all data coming from the models or should I just use the find_all_by_user_id for every controller function that has userdata in it?
If I understand you correctly you want to access some data by user_id. Which means that you can define relationship in the user model as has_many :this_and_that or something like that. It it is right, then you can create a before_filter or even better a function in your application controller in which you get your current user instance. Trough this instance, you can access all available data to that user. You can even make that function a helper function, and you can use that in a view.
#User.erb
has_many :other_data
#ApplicationConroller.erb
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
#OtherControllers
def index
#other_datas = current_user.other_datas
end
You can have before_filter :load_user_data, :if => current_user or something like that.
Method will look like that:
def load_user_data
#data = ModelName.where(user_id: session[:user_id])
end
#data will be ActiveRecord::Relation and will be chainable. Also using find_all_by_user_id in each controller not that bad, if you really need it.

Ruby on Rails security vulnerability with user enumeration via id

With Ruby on Rails, my models are being created with increasing unique ids. For example, the first user has a user id of 1, the second 2, the third 3.
This is not good from a security perspective because if someone can snoop on the user id of the last created user (perhaps by creating a new user), they can infer your growth rate. They can also easily guess user ids.
Is there a good way to use random ids instead?
What have people done about this? Google search doesn't reveal much of anything.
I do not consider exposing user IDs to public as a security flaw, there should be other mechanisms for security. Maybe it is a "marketing security flaw" when visitors find out you do not have that million users they promise ;-)
Anyway:
To avoid IDs in urls at all you can use the user's login in all places. Make sure the login does not contain some special characters (./\#? etc.), that cause problems in routes (use a whitelist regex). Also login names may not be changed later, that can cause trouble if you have hard links/search engine entries to your pages.
Example calls are /users/Jeff and /users/Jeff/edit instead of /users/522047 and /users/522047/edit.
In your user class you need to override the to_param to use the login for routes instead of the user's id. This way there is no need to replace anything in your routes file nor in helpers like link_to #user.
class User < ActiveRecord::Base
def to_param
self.login
end
end
Then in every controller replace User.find by User.find_by_login:
class UsersController < ApplicationController
def show
#user = User.find_by_login(params[:id])
end
end
Or use a before_filter to replace the params before. For other controllers with nested resources use params[:user_id]:
class UsersController < ApplicationController
before_filter :get_id_from_login
def show
#user = User.find(params[:id])
end
private
# As users are not called by +id+ but by +login+ here is a function
# that converts a params[:id] containing an alphanumeric login to a
# params[:id] with a numeric id
def get_id_from_login
user = User.find_by_login(params[:id])
params[:id] = user.id unless user.nil?
end
end
Even if you would generate random INTEGER id it also can be compromted very easy. You should generate a random token for each user like MD5 or SHA1 ("asd342gdfg4534dfgdf"), then it would help you. And you should link to user profile with this random hash.
Note, this is not actually the hash concept, it just a random string.
Another way is to link to user with their nick, for example.
However, my guess is knowing the users ID or users count or users growth rate is not a vulnerability itself!
Add a field called random_id or whatever you want to your User model. Then when creating a user, place this code in your UsersController:
def create
...
user.random_id = User.generate_random_id
user.save
end
And place this code in your User class:
# random_id will contain capital letters and numbers only
def self.generate_random_id(size = 8)
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
key = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
# if random_id exists in database, regenerate key
key = generate_random_id(size) if User.find_by_random_id(key)
# output the key
return key
end
If you need lowercase letters too, add them to alphanumerics and make sure you get the correct random number from the kernel, i.e. Kernel.rand(62).
Also be sure to modify your routes and other controllers to utilize the random_id instead of the default id.
You need to add a proper authorization layer to prevent un-authorized access.
Let us say you you display the user information in show action of the Users controller and the code is as shown below:
class UsersController < ActionController::Base
before_filter :require_user
def show
#user = User.find(params[:id])
end
end
This implementation is vulnerable to id guessing. You can easily fix it by ensuring that show action always shows the information of the logged in user:
def show
#user = current_user
end
Now regardless of what id is given in the URL you will display the current users profile.
Let us say that we want to allow account admin and account owner to access the show action:
def show
#user = current_user.has_role?(:admin) ? User.find(params[:id]) : current_user
end
OTH authorization logic is better implemented using a gem like CanCan.

hash instead of id

I want to use auto-generated hash'es instead of auto-incremented integers in my activerecords as primary keys. This raises two questions:
how to perform this generation in
most efficient way?
how to handle possibility that
generated hash exists already in
table?
Regards,
Mateusz
If you want this because you don't want to show the id in the web url. You can use a gem like https://github.com/peterhellberg/hashids.rb
It creates a reversible hash from your database id so the hash does not need to be stored in the database.
Use it in your models to_param method.
class MyModel < ActiveRecord::Base
def to_param
Hashids.new("salt").encode(id)
end
end
And decode the hash before finding the record from the database.
def show
id = Hashids.new("salt").decode(params[:id]).try(:first)
record = MyModel.find(id)
end
It might not be exactly what you asked for. But I had a similar problem where I wanted to use a hash instead of the ID in the URL. My solution follows
I added a column in my table called privatelink
In my model i wrote:
#changes the url to use privatelink instead of the id
def to_param
privatelink
end
#calls the private method set_privatelink
before_create :set_privatelink
private
#generates a unique hash looking something like this: c24bea1693d9e56a1878cb83f252fba05532d9d0
def set_privatelink
self.privatelink = Digest::SHA1.hexdigest([Time.now, rand].join)
end
Source:
Railcast #63 Model Name in URL - shows how to use the to_param method
It's not a duplicate of your question, but i think you want to do the same thing :
Assigning Each User a Unique 100 character Hash in Ruby on Rails
When using Oracle i had the case where I wanted to create the ID ourselves (and not use a sequence), and in this post i provide the details how i did that. In short the code:
# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
alias_method :orig_next_sequence_value, :next_sequence_value
def next_sequence_value(sequence_name)
if sequence_name == 'autogenerated'
# we assume id must have gotten a good value before insert!
id
else
orig_next_sequence_value(sequence_name)
end
end
end
while this solution is specific to Oracle-enhanced, i am assuming inside the other adapters you can overrule the same method (next_sequence_value).

Resources