Enforcing :has_one association - checking before def new method

Hi,

I am working on a Rails project. I am using devise for Users. Each user can create ONE profile so I have has_one profile in the users model and belongs_to users in the profile model.

I am wondering what the best way is to ensure that once a user has created a profile, they are not allowed to create another one.

I assume I want something in my
def new

end

in the Profiles Controller but I am not 100% sure how to handle this. Any advice would be great.

My project is located here: https://github.com/jkent2910/gopupgo

Thanks!!!

Hey Julie,

For a simple solution using your design, you would perform a before filter:

before_filter :check_for_existing_profile, only: [:new, :create]

private

def check_for_existing_profile
  if current_user.profile
    redirect_to current_user.profile, notice: 'Some message about already having a profile'
  end
end

I would also ensure that your users don’t have the option to create a profile in the UI if they have a profile already to avoid confusion or frustration.


Longer discussion:

If we can assume a user should have a profile then you could create the profile for them during the user creation and not allow the user to create profiles at all. You would then just have the show, edit and update actions on your ProfilesController and in your routes. Also this simplifies your UI by not needing to show or hide actions based on the presence of a profile. However this eliminates the destroy action as well and I am not sure what the use cases are for this and that may be an issue for you.

I would also be tempted to not have the profile concept at all and throw the eight additional fields on the user model itself. However there may be reasons not to do this either that I am not aware of in your app.

Hope that helps a bit,

Frank

Hi Frank,

Thank you so much for your reply. I do have a couple of questions.

I was able to get this to work, but I had to change before_filter to before_action. What is the difference between the two?

For the rationale behind having a user and a profile model… I think I want people to be able to sign-up without immediately having to create a profile. I’m still in the early stages of developing this idea, so that could change, but that was my reasoning at least for now. I do agree that I need to hide the “New” button for people who already have a profile created.

One other thing I have been struggling with… I do not want users to be able to edit or delete profiles of others. The easy way to do this is to not show the Edit and Delete buttons on any profile other than their own. I have been trying to get this to work but have been unsuccessful. I have been trying to put an if… else… in the view itself for show.html.haml but assume that this isn’t the best way to go about it. Any ideas?

Thank you again!!!
Julie

before_filter is the now deprecated version of before_action. I just need to get used to that as I have mostly rails 3 apps which use before_filter.

When I have a few more moments, I will see if I can help with your other question.

I have been trying to get this to work but have been unsuccessful.

What is happening exactly?

Hiding functionality in the UI is just security by obscurity, which is not at all secure. Just hiding the UI features does not prevent a user from submitting the actions directly through some other means. There are libraries that help with authorization, such as cancancan and pundit. I would look into those libraries for securing your objects (I like pundit).

But for a simple solution, you could hide the elements in the UI using your if statement and then use a before_action to ensure the profile they are editing or deleting is theirs. Something simple:

before_action :ensure_profile_ownership, only: [:edit, :update, :destroy]

private
def ensure_profile_ownership
  if current_user.profile_id != params[:id]
    redirect_to profile_path(params[:id]), notice: 'Some message about not having access to perform that action'
  end
end

Note this code is generally what cancancan and pundit do for you and I recommend using one of these if your authorization strategy gets any more complicated than this.

Is there a way to simply hide the edit and delete buttons in the view itself if that profile doesn’t belong to the user? That is what I’m trying to do.

Nevermind. I figured it out. :slight_smile: