Rails 4: Profile for users - ruby-on-rails

I have a Rails 4 app with a User model and it has_one :client where Client is the model where I store information about the client. I want my users to be able to view a profile page with their client information but I'm not sure which action to use or how to route to it.
I want to be able to view a particular client using the show action:
def show
#user = User.find(params[:id])
#client = #user.client
end
...but I also want a route for "my profile" which would rely on current_user and not finding a user by their ID. In my mind this is also a show action and I'm trying to avoid adding custom methods and would rather find the "railsy" way to do this.
def show
#user = current_user
#client = #user.client
end
I know this is sorta philosophical, but if someone could explain the proper way to achieve this I would appreciate it.

Actually, I am not sure what you are trying to do. If you want to add a specific page about showing current_user profile. Why not adding a new method?
Like this
def profile
#client = current_user.client
end
Or maybe you can validate the params[:id] in show action
Like this
def show
#user = params[:id].present? ? User.find(params[:id]) : current_user
#client = #user.client
end

Related

Session in Action Mailer - how to pass it?

Let's say I have a website where people can get a free ebook if they will sign up for a newsletter - after they've done it, I will create a User model and I will show them Edit Form to add some extra details about them.
I don't want to force them to add a password or any other details on the first page because it would decrease conversions and I don't require the additional information either. Also, I don't want them to have forever access to the Edit page so I solved it by assigned a session to them and recognize it through it on the Edit page. This is my controller:
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
UserWorker.perform_in(5.minutes, 'new_user', user.id)
redirect to edit form...
end
end
def edit
#user = User.find(session[:user_id])
end
def update
#user = User.find(session[:user_id])
#user.update!(user_edit_params)
redirect_to user_thank_you_path
end
end
But if they won't add extra information within 10 mins, I will send them an email via ActiveMailer with a link to the Edit form and ask them to do so.
Th question is how could I identify the user through the session and show them the form - how could I do User.find(session[:user_id] via ActionMailer)? Is it actually a correct way or would you recommend a different approach?
One way could be to set a background job to run in 10 minutes.
Inside that job, you would check if they're still "unregistered". You deliver the email if they've yet to complete the registration.
Something like this;
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
RegistrationCompletionReminderWorker.perform_in(10.minutes, user.id)
# redirect to edit form...
end
end
end
class RegistrationCompletionReminderWorker
def perform(user_id)
user = User.find(user_id)
if user.password.nil? # or whatever your logic for registration completion is
UserMailer.registration_reminder(user_id).deliver_now
end
end
end

Multiple forms in Rails

Not sure whether my database architecture is correct for rails. However below is my database architecture
Database Relations
Each User instance has only one PhoneBook instance.
A single Phonebook instance can have multiple Contact instances
A single Contact instance can have multiple Mobile instances
A single Contact instance can have multiple Email instances
The question is how should I implement my controller and views if I want to add a new contact for a signed in user in his phonebook.
you can do that with accepts_nested_attributes_for:, like a nested form
you could define the current user like so
controllers/application_controller.rb
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
# or find_by_authtoken!(...)
end
then you could do
controllers/phonebooks_controller.rb
def create
#phonebook = Phonebook.create(phonebook_params)
if #phonebook.save
# redirects here
end
end
.....
def phonebook_params
params.require(:phonebook).permit(:phonebook_params....).merge(:user_id => current_user)
end
and in your contacts controller
controllers/contacts_controller.rb
def create
#contact = Contact.create(contact_params)
if #contact.save
# redirects here
end
end
.....
def contact_params
params.require(:contact).permit(:contact_params....).merge(:user_id => current_user, :phonebook_id => current_user.phonebook)
end
Like that, you can use your forms in a simple manner, without having to generate routes like /user/id/phonebook/id/contacts
in addition to the links below the first answer, maybe have a look at this basic form. It it is not a direct answer to your question, but maybe it'll help you getting an idea of how a form could look like.

Authorize related object when using pundit gem

If I have user object and user has one basicinfo. In user show action I have:
def show
#user = User.find params[:id]
authorize #user
end
And in show.html.erb I must show user's basicinfo, such as:
User name is: <%= #user.basicinfo.name %>
In this case should I also authorize basicinfo in user show action?
def show
#user = User.find params[:id]
authorize #user
authorize #user.basicinfo, :show?
end
The authorization applies to the entire action.
If you want to filter out some elements in the view you can do so on an ad hoc basis, basically applying whatever attribute you are using in the xxxPolicy class (which is not provided above)
Handling user authorization is possibly too complicated via Pundit
def initialize(user, user)
I definitely do filtering in the views when it comes to user actions

Cancan how to authorize collection model in one controller?

I am using cancan with rails 4. My user model has many datings and a dating has many reply. In the user controller's show action, I want to authorize all of them:
#user = User.find params[:id]
authorize! :read, #user
#datings = #user.first_page_datings
authorize! :read, #datings
# How to authorize replies of all the datings here?
Question is how to authorize replies of all the datings here?
Instead of trying to authorize all the child objects, use #dating = #user.datings.accessible_by(current_ability) to load the permitted ones.

Rails Security: redirect if not current_user

I've been using authlogic and it works really well. One thing I've noticed is that as a hacker I could easily type in this address:
localhost::3000/users/a_user_that_is_not_me/edit
Since the form in the edit form is for #user which is set to current_user and requires an authenticity token, even if I tried to put in details for the other user I end up changing my own account instead of the other users.
That's nice and good, but I'd like it so that these hackers get redirected before they even see the form.
I tried this in the users_controller:
def edit
if admin?
#user = params[:user]
elsif User.find_by_username(params[:id]) != current_user
#user = current_user
#not_user = User.find_by_username(params[:id])
redirect_to user_path(#not_user)
else
#user = current_user
end
end
The redirect works if I type in an address with another user's name but I get a 404 error when trying to access the edit page for the current user.
Any ideas why this doesn't work?
If you're going to be doing this kind of thing a lot, check out an authorization plugin like authorization-san.
Authorization differs from authentication in that authentication is logging in, but authorization pertains to the authenticated (or un-authenticated) user's rights to perform actions.
With authentication-san, you could define this rule with this piece of code in your controller:
# this assumes you've got some way to set #user to the user you're looking up,
# e.g. in a before_filter
allow_access(:authenticated, :only => [:edit, :update]) { current_user == #user }
It looks like you are assigning #user to a string if the current user is an admin. This is simpler (less typo-prone):
def edit
u = User.find_by_username!(params[:id])
if admin? or current_user.username == params[:id]
#user = u
else
redirect_to user_path(u)
end
end
Also, don't you want to use find_by_username! (with bang on end) so that a 404 page is rendered when the user is not found? I'm not sure how you're getting the 404 page now...

Resources