Rails polymorphic user model with Devise - ruby-on-rails

So I know this question has been ask a ton of times but my question goes a little bit further.
When modeling my application I have two types of users that have a polymorphic association to the user model. Such as:
class User < ActiveRecord::Base
belongs_to :profileable, :polymorphic => true
end
class User_Type_1 < ActiveRecord::Base
has_one :user, :as => :profileable
end
class User_Type_2 < ActiveRecord::Base
has_one :user, :as => :profileable
end
The reason I did this, instead of an STI, is because User_Type_1 has something like 4 fields and User_Type_2 has something like 20 fields and I didn't want the user table to have so many fields (yes 24-ish fields is not a lot but I'd rather not have ~20 fields empty most of the time)
I understand how this works, my question is I want the sign up form to only be used to sign up users of type User_Type_1 but the sign in form to be used to both. (I will have an admin side of the application which will create users of User_Type_2)
I know I can use the after_sign_in_path_for(resource) override in AppicationController somehow to redirect to the right part of the site on sign in. Something like:
def after_sign_in_path_for(resource)
case current_user.profileable_type
when "user_type_1"
return user_type_1_index_path
when "user_type_2"
return user_type_1_index_path
end
end
So I guess my questions are how would I make the form to work with Devise and only allow signups of type User_Type_1 and then sign them in after sign_up?
Also, if I am going about this the wrong way, what is the right way?

I was able to answer my own question and am putting it here so that maybe it can help someone else with the same problem.
The login problem was easy, just use the default devise login and the after_sign_in_path_for in ApplicationController as described above
I realized the answer to the form question as typing it out here:
I just created a normal form for the User_Type_1 with nested attributes for User
and had it post to the UserType1Controller
Then saved both objects and called the sign_in_and_redirect helper from Devise
class UserType1Controller < ApplicationController
...
def create
#user = User.new(params[:user])
#user_type_1 = UserType1.new(params[:patron])
#user.profileable = #user_type_1
#user_type_1.save
#user.save
sign_in_and_redirect #user
end
...
end
Then the after_sign_in_path_for method from above sent it to the right place and it was all good.

Related

Rails, Devise, Pundit - authorise Profile created from Devise registration controller

Feel free to say if you think something is wrong.
I extended Devise Registration controller to create a Profile object to every new user:
class Users::RegistrationsController < Devise::RegistrationsController
def new
resource = build_resource({})
resource.profile = Profile.new
resource.profile.user_id = #user.id
respond_with resource
end
They both are has_one - has_one related and in database:
create_table :profiles do |t|
t.belongs_to :user, index: { unique: true }, foreign_key: true
end
So to get the right profile of current user, I must:
private
def set_profile
#profile = Profile.where(user_id: current_user.id).first
end
And this kinda solves the problem - seems other users cant go around this query and access other profiles (or CAN THEY?), but for other resources I use Pundit to control authorisation, so now it feels a bit messy.
So thats one concern. Other - I still don't know how to act when there is no user logged, because if visiting any restricted resource, this:
private
def set_some_resource
end
end
Throws - "undefined method `id' for nil:NilClass) - how is best to avoid this?
Thanks for any advices.
You may want to start by reading the Rails guides on assocations.
To create a one to one association you use belongs_to on the side with the foreign key column and has_one on the other.
class User
has_one :profile
end
class Profile
belongs_to :user
end
ActiveRecord then automatically links the records together. In general you should avoid setting ids (or getting associated records by ids) explicitly and instead use the assocations:
class Users::RegistrationsController < Devise::RegistrationsController
# ...
def new
# calls Devise::RegistrationsController#new
super do |user|
user.profile.new
end
end
end
Devise is pretty nifty and lets you pass a block to tap into the flow instead of copypasting the whole action.
Simularily you would fetch the current users profile with:
private
def set_profile
#profile = current_user.profile
end
You can set if the callback should be called by using the if: option.
before_action :set_profile, if: :user_signed_in?
But if the action requires authentication you should make sure that it is after :authenticate_user! anyways which will halt the filter chain.
And this kinda solves the problem - seems other users cant go around
this query and access other profiles (or CAN THEY?), but for other
resources I use Pundit to control authorisation, so now it feels a bit
messy.
You don't need to use Pundit to authorize creating a profile or fetching the current users profile. Since the profile is fetched via the user the is no way for another user to access it (well without hacking).
what you might want to authorize is the show, index, edit etc actions if you create a ProfilesController.

validation error on update_attributes of a subclass (STI)

I implemented a signin/out machinery as Michael Hartl suggested in his tutorial (http://ruby.railstutorial.org/chapters/sign-in-sign-out). All worked perfectly: creating, deleting, updating user from a profile page.
Then I created a Teacher model as a subclass of User (STI). It hasn't its own validation and should use the same validation as parent model (user).
But updating attributes for user with type = "Teacher" is no more possible (for the others users it continues to work). When I compile the update form it returns:
`error messages say me teacher.attributes.password is missing validation
while the teacher should inherit the same validation of user.
In routes.rb, after the creation of the teacher model I added:
resources :teachers, :controller => 'users', :type => "Teacher"
This is the update method in the user controller:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to #user ...
If I change #user.update_attributes(params[:user]) to #user.update_attributes(params[:teacher]) It works, but obviously only for teacher.
I think the solution is quite easy but I'm a novice.
Any help would be appreciated!
I solved my problem thanks to the solution proposed by d11wtq here:
Is subclassing a User model really bad to do in Rails?
I edited the Teacher model like this:
class Teacher < User
def self.model_name
User.model_name
end
end

Mass assignment and roles in Rails

I have Users, Roles, and Network models in Rails 3. Users and Networks have a HMT relationship through Roles. If a User has an Role type of 'admin' with a specific Network, he/she can change the Role of other Users.
It seems like the Role type should not be available via mass-assignment. So how does a Network admin change the position type of other Users? I actually think this is a really basic question, but I just can't find the answer.
I am using CanCan and have a current_user method. Does that simply mean the controller would have a simple if/then check to see if the user has the appropriate role in a network?
Does mass assignment only apply to pages that have no authentication?
So a couple things
You'll presumably display a role select box on the users/1/edit page if the user is an admin
On the update action on the controller, you'll have some kind of authorization to make sure the user is an admin
When the time comes to make the update
:
class myModel < ActiveRecord::Base
attr_accessible :some_attr, :other_attr
attr_accessible :some_attr, :other_attr, :role_id, :as => :admin
end
class myController < ApplicationController
#admin check before doing the following
if #user.update_attributes(params[:user], :as => :admin)
redirect_to root_path
end
end

Commenting system for RoR Blogging Platform

I'm trying to design a comment system for my RoR blogging site, and I am having some conceptual problems with the architecture. As far as models are concerned, I have Blogposts, Users, and Comments.
A User has_many Blogposts
A Blogpost belongs_to one User
A Blogpost has_many Comments
A Comment may or may not belong to a registered User (I want people not registered with the site to be able to comment as well).
My question is this: in order to enforce the link between a comment and a blogpost, I create each new comment (#comment) through the blogpost association (#blogpost.comments.build(:args)). However, I do not know how to associate a particular registered User with his/her comment. I left the user_id attribute OUT of the attr_accessible for the Comment model because I wanted to prevent the possibility of people attributing comments to the wrong users.
Any ideas on how best to implement a commenting system with such a relation? Thanks so much in advance!
Assuming:
User has_many comments
Comment belongs_to user
In your controller when saving the comment, you can simply do:
#comment.user = current_user if current_user
#comment.save
If the comment is done by an unregistered user #comment.user just stays empty.
You can just have an association :
User has_many comments through blog_posts
So, now you can do :
current_user.comments
Another way to do it is via blog_post:
current_user.blog_post.comments
Moreover, you can use the nice act_as_commentable plugin :)
https://github.com/jackdempsey/acts_as_commentable
There's no need to have user_id as attr_accessible if you have access to the currently logged in user in your save or post new comment methods.
If they aren't logged in then you expect current user to be empty / false.
This should be available if you're using any of the authentication plugins such as authlogic or devise. In my experience with authlogic you typically have a current_user method in your ApplicationController.
class ApplicationController
helper_method :current_user_session, :current_user
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
end
Above code from the Authlogic quick example
You can add an association between Comment and User, then create the comment with current_user:
# User.rb
has_many :comments
# Comment
belongs_to :user
Setting up the associations only really adds the association methods, so there's no problem with creating Comment without a logged in user. You don't want to build the comment off of current_user as current_user.comments.create(...), because that will throw a NilClass error if nobody is logged in.
#user = current_user # #user should be nil if commenter is not logged in
# be fancy and use a block
#blogpost.comments.create(params[:comment]) do |c|
c.user = #user
end
As long as there is no validation for User in Comment, the nil user should just pass through without trouble.

Rails authentication gift list for each user?

I am trying to get to grips with the basics of authentication in Rails. To start with I have used the nifty_authentication generator by Ryan Bates. It's helping me learn the basic options for user logins etc.
I have a simple application the has a person and gift table in the database. The idea is, each user creates a list of people and then assigned possible gifts to each of those people.
So from a structural point of view:
person belongs to user
gift belongs to person
So I have the models set up as follows.
person model
class Person < ActiveRecord::Base
has_many :gifts
end
gift model
class Gift < ActiveRecord::Base
belongs_to :person
end
user model
currently doesn't contain any belongs_to has_many etc.
How do I go about making sure each user has their own list of people. So one user cannot see another users list of people or gifts.
Would I simply add the following to the user model?
has_many :people
and the following to the person model?
belongs_to :user
Would that work, or am I missing something?
Thanks,
Danny
UPDATE:
The app so far is on Heroku and Github.
http://giftapp.heroku.com/
http://github.com/dannyweb/GiftApp
Would that work, or am I missing
something?
Very short answer: yes that would work; no you are not missing something.
I looked at your code.
Instead of:
def index
#people = Person.find(:all)
end
You need something along the lines of:
def index
#people = current_user.people
end
Where current_user is the User object that refers to the logged in user.
In the create method you will need to assign the newly created person to the current_user:
def create
#person = Person.new(params[:person])
#person.user = current_user # This associates #person with current_user
if #person.save
flash[:notice] = "Successfully created person."
redirect_to #person
else
render :action => 'new'
end
end

Resources