2

I've got three classes Admin, Client, Agent which all inherit from User < ActiveRecord::Base. The system is designed using STI, hence all classes share the same users table.

I am interested in keeping the CRUD functionality pretty much the same, hence for now I am using a single UsersController.

When implementing the Update functionality I'm faced with a doubt. Here's my edit form:

#Edit Form
<%= form_for(@user,{url:user_path(@user),method: :put}) do |f| %>
    <%= render 'edit_fields',f:f %>
    <%= f.submit "Save", class: "btn btn-large btn-primary"%>
<% end %>
#UsersController
def edit
    @user=User.find(params[:id])
end
def update
    binding.pry
   #if @user.update_attributes(params[:user])   #<---BEFORE
   #WORKAROUND BELOW
   if @user.update_attributes(params[@user.type.downcase.to_sym])
       flash[:success]="User was updated successfully."
       redirect_to user_path(@user)
   else
        flash[:danger]="User could not be updated."
        render 'new'
   end
end

My "problem" is that params is dependent on the @user.type of the @user instance. Therefore sometimes there's a params[:client], other times a params[:admin] or params[:agent]. Hence the line if @user.update_attributes(params[:user]) does not always work.

The workaround I implemented works fine, but I was wondering whether there's a more DRY or elegant way to approach these issues, i.e. sharing CRUD between different STI-ed classes.

lllllll
  • 4,715
  • 6
  • 29
  • 42
  • 1
    Why can't you always send `:user`? And if you can't for some reason, why not use a separate controller, or use a helper method to sniff out the correct key? – PinnyM Feb 10 '14 at 19:20
  • 1
    `params` is a `HashWithIndifferentAccess` which means the `to_sym` call when accessing it is unnecessary. – tadman Feb 10 '14 at 19:22
  • @PinnyM: I'm not sure using a separate controller for CRUD functionality would be such a DRY idea, CRUD behaves basically the same for all classes, except the `type` ofc. I guess that's why I thought STI would be a good choice. As for always sending `:user`, I guess you mean editing the form so the symbol is always `:user`, that is a good point, I just wanted to keep the `form_for` instead of `form_tag`, you are right tho.thanks! – lllllll Feb 10 '14 at 19:30
  • instead of `params[@user.type.downcase.to_sym]` I used `params[@user.type.underscore.to_sym]` to make it safe for models like FooBar – coderuby Jun 10 '14 at 19:29

2 Answers2

7

There is indeed a much more elegant solution. Just change your form_for declaration and add the as option, like this:

<%= form_for(@user, as: :user, url: user_path(@user), method: :put) do |f| %>

That way in your controller your parameters will be scoped under the user key instead of the model's class.

Nikos
  • 1,052
  • 5
  • 7
1

In your controller, check for the User type in a before_filter as below. I've used this for similar STI Controllers and works great for me.

before_filter :get_user_type

private

def get_user_type
   @klass = params[:type].blank? ? User : params[:type].constantize
end

And then for example you could call a show method as :

def show
  @user = @klass.find params[:id]
  #render
end

Using @klass across your CRUD actions should simplify your Controller.

blotto
  • 3,387
  • 1
  • 19
  • 19
  • I'm not sure I prefer this over my approach( which I believe is more readable/straighforward). Good idea tho, thanks. – lllllll Feb 10 '14 at 19:45