1

I'm trying to split the users edit page (app/views/devise/registrations/edit.html.erb) into 2 pages for better UI, like:

/settings 
/settings/profile

I'm fairly new to Rails, did Michael Hartl's tutorial and had read a few more I got my hands on, just building my first application, even if I have some experience with php

This is the view I try to split in 2, it is a view provided by the Devise gem (app/views/devise/registrations/edit.html.erb)

<h2>Login Details</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <%# sensitive info %>
  <%= f.label :email %><br />
  <%= f.email_field :email, autofocus: true %>
  ....
  <%= f.label :current_password %> <i>(to confirm changes)</i><br />
  <%= f.password_field :current_password, autocomplete: "off" %>

  <h2>Profile Details</h2>

  <%# non-sensitive info %>
  <%= f.label :name %><br />
  <%= f.text_field :name %>

  <%= f.submit "Update" %>
<% end %>

It uses a custom RegistrationsController (app/controllers/registrations_controller.rb)

class RegistrationsController < Devise::RegistrationsController
  def update
    ....
  end
end

Further more, this view is accessed via this route:

edit_user GET      /users/:id/edit(.:format)              users#edit

My main question is, how do I split this page into 2:

  • /settings containing Login Details
  • /settings/profile containing Profile details

and both to processed by the same controller, or the same action

Do I need to create a new controler/route/view, like:

  • controller: SettingsProfile
  • route: get 'settings/profile' => 'settings_profile#new'
  • view: app/views/settings_profile/new.html.erb

If so how do I pass the view the "resource" information, or any information for the matter of fact:

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>

Things are pretty fuzzy at this point, please bear with me on this one

3 Answers 3

1

You don't need a separate controller, especially since you're already extending the default Devise RegistrationsController, which already works fine for updating user attributes.

Edit: If these aren't just extended user attributes, and profile is it's own object with its own logic and behaviour, then consider creating it's own controller, to manage the CRUD for that object.

If you're using devise's user/edit page as part one, all you need to do is add a profile action in your custom controller, and create a view file to go with it.

# this is all that's in the edit action from Devise
def edit
  render :edit
end

# add this to your custom RegistrationsController
def profile
  render :profile
end

Then you can fiddle with your routes (see this and this) until they route the URLs you want to use to the correct controller:

# you probably have this, which covers your current user/edit route
devise_for :users

# but you can add this to extend these routes
devise_scope :user do
  # will route /profile to the profile action on User::RegistrationsController
  get :profile, to: 'users/registrations'

  # or if you want more control over the specifics
  get 'users/settings/profile', to: 'users/registrations#profile', as: :user_profile
end

For your second view/form to update user attributes from another, non-devise controller, you can use form_for current_user, { url: user_registration_path }

If you do want to use resource, you'll have to add this to the top of your registrations controller, so that the resource gets defined on your profile action as well:

prepend_before_filter :authenticate_scope!, only: [:edit, :profile, :update, :destroy]

Take a look at devise's documentation around strong parameters to see how to make sure whatever additional attributes you're going to add to your user are white listed by your custom RegistrationsController.

12
  • I agree - the separate controller isn't necessary. However there are two reasons I chose that path. The first is it matches the OP's thinking in a way, and when possible I try to provide an answer that isn't too out-of-band from the OP's intuition (unless of course doing so would be harmful). Second, I find the coupling of the controller to the data model to be an enduring and strange Rails-ism. By creating a separate user profile controller and a separate route, future changes to the model will have no impact on the API of this app. e.g. if the profile is factored out of User model
    – user44484
    Commented Dec 9, 2015 at 19:50
  • I also came from PHP when I first started learning Rails, and in hindsight I feel the mindset I came in with was mostly wrong. I fell in love with Rails because of how frequently I said to myself "oh wow, I didn't actually need to do ANY of all that" after learning more about the conventions. While I'd normally try to stick with the OP's intuition on how they're looking to implement something, I feel like this is an opportunity for the OP to enjoy some of the comforts of convention over configuration, and not do the needless.
    – sicks
    Commented Dec 9, 2015 at 19:58
  • Also, I'm not sure what you mean in regards to the coupling of controller data to the model? I'm not touching the model at all here, once any new attributes are white listed in the controller all we're doing is pointing an extra route to an extra view.
    – sicks
    Commented Dec 9, 2015 at 20:01
  • Oh absolutely CoC is a great burden lifter, especially if OP has no reason to care about API consumers at all, or if refactoring their app will have no impact on anyone but themselves.
    – user44484
    Commented Dec 9, 2015 at 20:02
  • So in Rails' CoC you often have e.g., resource model like User, and then a set of views associated to it, and a UserController. And due to the Rails conventions a lot of things automatically work, e.g., parameters sent into the UserController will be enveloped in a JSON struct named "user". And then also Strong params can make use of that to prevent mass assignment, etc. There's a lot of good there. (cont'd)
    – user44484
    Commented Dec 9, 2015 at 20:04
1

As you suggested, one method would be to create a new controller, route, and view to handle this.

I might create a UserProfilesController controller with two actions: UserProfilesController#show and UserProfilesController#edit.

Then as you suggested, a route, e.g.,

get 'user_profiles/:id' => 'user_profiles#show'
get 'user_profiles/:id/edit' => 'user_profiles#edit'

In Devise parlance, the resource refers to the user. So the :id being passed above must be a User id of course. If you don't want to do that, you could always just assume you meant the current_user in which case you can skip using :id in the routes and just retrieve it in the controllers via current_user.id.

Finally, you just have to split out the profile details from the Devise view and create some under app/views/user_profiles/new.html.erb and similarly for edit.html.erb. Remember to remove the profile bits from the Devise view and I think you're on your way.

An Addendum

@AmrNoman made a good suggestion re: the update method. If you are following with my solution, you would add another action UserProfilesController#update, and a new route in your routes.rb file:

put 'user_profiles/:id/update' => 'user_profiles#update'

Additionally, if you intend to later refactor User to remove the profile details and handle them in a separate model, it may be prudent to replace my references to :id in the above code to :user_id. In this way, if you at some point create, e.g., a model called UserProfile it will be clearer that the :id is not the UserProfile#id but the UserProfile#user_id foreign key. This will leave you the ability to use :id to refer to UserProfile.id directly without affecting any API consumer in your app.

It may be a bit overkill but I think it's good practice.

2
  • Why go to user_profiles#new in the first route? I think that in both cases he wants to edit (just different information), so shouldn't it be something like user_profiles#edit_credentials instead ?
    – Amr Noman
    Commented Dec 9, 2015 at 17:13
  • It was a typo @AmrNoman. Thanks for pointing that out. I've edited it to show as I originally intended.
    – user44484
    Commented Dec 9, 2015 at 17:14
0

Chris Cameron's answer is right, but I think this is closer to what you want:

First create your routes in routes.rb:

  # this gets the edit page for login details
  get "settings" => "user_profiles#edit_credentials", as: "edit_credentials"

  # this gets the edit page for other profile info
  get "settings/edit" => "user_profiles#edit_profile", as: "edit_profile"

  # update method shared by both forms 
  # (you can create another one to handle both forms separately if you want)
  put "settings" => "user_profiles#update"

Then your controller user_profiles_controller.rb:

class UserProfilesController < ApplicationController
  # fill the methods as you need, you can always get the user using current_user

  def edit_credentials
  end

  def edit_profile
  end

  def update
  end
end

and finally your views, first views/user_profiles/edit_credentials.erb.html, here you show form for login details:

<%= form_for(current_user, url: settings_path) do |f| %>
<% end %>

then same thing in views/user_profiles/edit_profile.erb.html, just change your form contents:

<%= form_for(current_user, url: settings_path) do |f| %>
<% end %>

There might be some errors, but hopefully this gets you in the right direction (also make sure to handle authentication and authorization).

1
  • ah and you also though to include the put/update method which I did not. I'll have to amend my answer later. Good thinking.
    – user44484
    Commented Dec 9, 2015 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.