Rails 3 模型子类让我抓狂!请帮助纠正 url/params 哈希值

发布于 2024-11-09 11:07:49 字数 3516 浏览 0 评论 0 原文

我在子类化 Rail 模型时遇到问题。假设您有一个 User 模型及其存储特定方法和关联的几个子类(用户类型),例如:DirectorAdminTraineeInstructor等等,这只是简单的“单表继承”。问题有两个。

  1. 当您传递子类而不是基类时,路径/网址经常崩溃或做奇怪的事情。这是一个例子:

    <% if user.enabled? %>
      <%= link_to '禁用',disable_user_path(用户) %>
    <%其他%>
      <%= link_to '启用',enable_user_path(用户) %>
    <%结束%>
    

    如果您传入 User 模型,它就可以正常工作。但是如果你传递一个子类,比如Admin,它会抛出这个异常:

    没有路由匹配 {:controller=>"users", :action=>"disable", :id=>#}
    

    显然,这行为不正确。我们怎样才能让rails始终使用基类?

  2. 更令人不安的是表单(我使用的是 simple_form)。假设您有一个 /profile 表单。您希望所有 User 子类都能平等地访问它,并且您不希望在特殊情况下处理它们的子类;它应该是 100% 通用的。

    出于某种原因,如果用户是管理员,它会将参数哈希发布为

    参数[:admin]
    

    更糟糕的是,如果您查看表单的源代码,它实际上显示的是 user[first_name] 而不是 admin[first_name],所以肯定有问题!实例变量也是@user,所以我不明白为什么它应该发布到params[:admin]

这是(2)的表单视图代码:

<%= simple_form_for(@user, :url => profiles_path, :method => :put, :html => {:multipart => true}) do |f| %>
  <%= f.error_notification %>

  <div class="form">
    <fieldset>
      <legend>Personal Information</legend>
      <%= f.input :first_name %>
      <%= f.input :last_name %>
    </fieldset>

    <fieldset>
      <legend>Credentials</legend>
      <%= f.input :email %>
    </fieldset>

    <fieldset>
      <legend>Preferences</legend>
      <%= f.input :receive_email_notifications %>
      <%= f.input :receive_newsletters %>
      <%= f.input :allow_private_messages %>
    </fieldset>

    <fieldset>
      <legend>Avatar</legend>
      <p>
          Select an image from your computer to use as your avatar. You will be given the oppurtunity
          to crop this image further after your image has been uploaded.
      </p>
      <%= f.input :avatar, :as => :file, :label => "Select File" %>
      <%= f.hidden_field :avatar_cache %>
    </fieldset>

    <div class="actions">
      <%= f.button :submit, :value => "Update Profile" %>
      <%= link_to 'Cancel', profiles_path %>
    </div>
  </div>

<% end %>

这是首先渲染视图和提交表单时的控制器操作:

  def edit
    @user = User.find(current_user.id)
  end

  def update
    @user = User.find(current_user.id)

    if @user.update_attributes(params[:user]) # <-- this bombs for Admin subclass
      if params[:user][:avatar] # <-- this would bomb also.
        redirect_to(crop_avatar_profiles_path)
      else
        redirect_to(profiles_path, :notice => 'Your profile was successfully updated.')
      end
    else
      render :action => "edit"
    end
  end

  def crop_avatar
    @user = User.find(current_user.id)
  end

  def update_avatar
    @user = User.find(current_user.id)
    @user.crop(params[:x].to_i, params[:y].to_i, params[:h].to_i, params[:w].to_i)

    redirect_to(profiles_path, :notice => 'Your profile and avatar was successfully updated.')
  end

除了想出一些相当不优雅的解决方案(特别是表单问题)之外,我还不知所措我该如何修复它们。必须有更好的方法来处理这些情况。

I have a problem with subclassing Rail's models. Suppose you have a User model and several subclasses of it (user types) that store specific methods and associations, for example: Director, Admin, Trainee, Instructor, etc. This is just simple "single-table inheritance". The problem is 2-fold.

  1. paths/urls often crash or do weird things when you pass a subclass rather than the base class. Here's an example:

    <% if user.enabled? %>
      <%= link_to 'Disable', disable_user_path(user) %>
    <% else %>
      <%= link_to 'Enable', enable_user_path(user) %>
    <% end %>
    

    If you pass in a User model, it works just fine. But if you pass a subclass, like Admin, it throws this exception:

    No route matches {:controller=>"users", :action=>"disable", :id=>#<Admin id: 1, first_name: "Ken", ..., created_at: "2011-05-23 21:01:35", updated_at: "2011-05-23 21:04:28">}
    

    Clearly, this is not behaving correctly. How can we get rails to use the base class all the time?

  2. Even more disturbing is forms (I am using simple_form). Let's say you have a /profile form. You want all User subclasses to access it equally, and you don't want to deal with their subclasses on a special-case basis; it should be 100% generic.

    For some reason, if the user is an Admin, it will post the params hash as

    params[:admin]
    

    Even worse, if you view the source of the form, it actually says user[first_name] instead of admin[first_name], so something is definitely screwy! The instance variable is @user too, so I don't see why it should be posting to params[:admin].

Here is the form view code for (2):

<%= simple_form_for(@user, :url => profiles_path, :method => :put, :html => {:multipart => true}) do |f| %>
  <%= f.error_notification %>

  <div class="form">
    <fieldset>
      <legend>Personal Information</legend>
      <%= f.input :first_name %>
      <%= f.input :last_name %>
    </fieldset>

    <fieldset>
      <legend>Credentials</legend>
      <%= f.input :email %>
    </fieldset>

    <fieldset>
      <legend>Preferences</legend>
      <%= f.input :receive_email_notifications %>
      <%= f.input :receive_newsletters %>
      <%= f.input :allow_private_messages %>
    </fieldset>

    <fieldset>
      <legend>Avatar</legend>
      <p>
          Select an image from your computer to use as your avatar. You will be given the oppurtunity
          to crop this image further after your image has been uploaded.
      </p>
      <%= f.input :avatar, :as => :file, :label => "Select File" %>
      <%= f.hidden_field :avatar_cache %>
    </fieldset>

    <div class="actions">
      <%= f.button :submit, :value => "Update Profile" %>
      <%= link_to 'Cancel', profiles_path %>
    </div>
  </div>

<% end %>

Here are the controller actions to first render the view and when I submit the form:

  def edit
    @user = User.find(current_user.id)
  end

  def update
    @user = User.find(current_user.id)

    if @user.update_attributes(params[:user]) # <-- this bombs for Admin subclass
      if params[:user][:avatar] # <-- this would bomb also.
        redirect_to(crop_avatar_profiles_path)
      else
        redirect_to(profiles_path, :notice => 'Your profile was successfully updated.')
      end
    else
      render :action => "edit"
    end
  end

  def crop_avatar
    @user = User.find(current_user.id)
  end

  def update_avatar
    @user = User.find(current_user.id)
    @user.crop(params[:x].to_i, params[:y].to_i, params[:h].to_i, params[:w].to_i)

    redirect_to(profiles_path, :notice => 'Your profile and avatar was successfully updated.')
  end

Besides thinking up some pretty inelegant solutions (especially to the forms problem), I am at a loss as to how I can fix them. There has to be a better way to deal with these situations.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

a√萤火虫的光℡ 2024-11-16 11:07:49
= simple_form_for @fruit, :as => :fruit do |form|

@fruit 可以是任何类(Orange、Apple、Pear),它仍然会在输入中添加“fruit”前缀,从而每次都会给出 params[:fruit]

= simple_form_for @fruit, :as => :fruit do |form|

@fruit can be any class (Orange, Apple, Pear) and it will still prefix the inputs with 'fruit' thus giving a params[:fruit] everytime.

懒猫 2024-11-16 11:07:49

由于动态铸造导轨对我造成了影响,我在 STI 上也遇到了同样的问题。我使用 becomes 方法解决了这个问题。

在您的用户模型中:

    #return an array [User, User, ...], instead of [Admin, Director, Director, Instructor, ...]
    def self.all_without_typecast
      self.all.collect! do |u|
          u.becomes(User)
      end
    end

    #makes subclasses of User (Admin, Director, ...) into User class
    def userize #cast_into_user could be a better name
      self.becomes(User)
    end

现在,只要您不希望用户实例属于特定类型(管理员、总监......)而只是用户,您就可以这样做:

    @user = @user.userize

并且您的 @user 对象现在将被视为用户,而不是Admin 或 Director 或其他什么,对于 all_without_typecast 也是如此,如下所示:

    @users = User.all_without_typecast # if you're having problems with User.all

也许这个方法可以在每个作用域之前运行,这样您就不必重写它们中的每一个。
希望有帮助!

I had the same problem with STI due to the dynamic casting rails did for me. I solved it using the becomes method.

In your User model:

    #return an array [User, User, ...], instead of [Admin, Director, Director, Instructor, ...]
    def self.all_without_typecast
      self.all.collect! do |u|
          u.becomes(User)
      end
    end

    #makes subclasses of User (Admin, Director, ...) into User class
    def userize #cast_into_user could be a better name
      self.becomes(User)
    end

Now whenever you don't want a user instance to be of an specific type (Admin, Director..) but just User, you can do:

    @user = @user.userize

And your @user object will now be treated as a User, not Admin or Director or whatever, same goes for all_without_typecast, like this:

    @users = User.all_without_typecast # if you're having problems with User.all

Maybe this methods could be run before each scope so you don't have to rewrite every one of them.
Hope it helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文