【问题标题】:Rails form_tag to show actionRails form_tag 显示动作
【发布时间】:2015-06-27 12:53:22
【问题描述】:

I have a Rails app where I have a partial that I want a dropdown in so when a user is selected it will do a get method to the urls /timecards/:user_id which will be the controller's show method passing in the User's标识字段。我在form_tag 的路径上遇到了困难,我需要一些帮助。

这是我的部分观点:

<%= form_tag timecard_path, :method => :get do %>
  <%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username) %>
  <%= submit_tag "View Employee", class: "btn btn-primary" %>
<% end %>

从 rake 路线我得到以下输出:

timecards GET    /timecards(.:format)                 timecards#index
                           POST   /timecards(.:format)                 timecards#create
              new_timecard GET    /timecards/new(.:format)             timecards#new
             edit_timecard GET    /timecards/:id/edit(.:format)        timecards#edit
                  timecard GET    /timecards/:id(.:format)             timecards#show
                           PUT    /timecards/:id(.:format)             timecards#update
                           DELETE /timecards/:id(.:format)             timecards#destroy

这是我的控制器:timecards_controller.rb

class TimecardsController < ApplicationController
before_filter :disallow_clients, :disallow_medics, :disallow_employee, :disallow_supervisor

  def index
    @clock_events = ClockEvent.includes(:user).search(params[:search])
    respond_to do |format|
        format.html do
          @clock_events = @clock_events.paginate(:per_page => params[:per_page] || 20, :page => params[:page]).order('users.username asc').order('clock_in desc')
        end
        format.csv { send_data ClockEvent.to_csv(@clock_events.order('users.username asc').order('clock_in desc')) }
      end
  end

  def new
    @clock_event = ClockEvent.new
  end

  def create
    parse_times!
    @clock_event = ClockEvent.new(params[:clock_event])

     if @clock_event.save
       redirect_to timecard_path(@clock_event.user.id), notice: "Entry added for #{@clock_event.user.username}".html_safe
      else
       render :new, notice: "Time Card Entry failed to Save".html_safe
      end
  end

  def show
    @user = User.find(params[:id])
    @clock_events = @user.clock_events.search(params[:search])
      respond_to do |format|
        format.html do
          @clock_events = @clock_events.paginate(:per_page => params[:per_page] || 5, :page => params[:page]).order('clock_in DESC')
        end
        format.csv { send_data ClockEvent.to_csv(@clock_events.order('clock_in desc')) }
        format.pdf do
          pdf = TimeCardPdf.new(@clock_events, @user)
          send_data pdf.render, filename: "timecard-#{@user.username}",
                                type: "application/pdf",
                                disposition: "inline"
         end
      end
  end

  def edit
    @user = User.find(params[:id])
    @clock_events = @user.clock_events.search(params[:search]).order("clock_in ASC").paginate(:per_page => 10, :page => params[:page])
  end

  def update
    parse_times!
    @clock_event = ClockEvent.find(params[:clock_event][:id])
    if @clock_event.update_attributes(params[:clock_event])
        redirect_to edit_timecard_path(@clock_event.user.id), notice: "Updated Successfully".html_safe
    else
        redirect_to :back, notice: "Woops.".html_safe
    end
  end

  private

  def parse_times!
    params[:clock_event].parse_time_select! :clock_in if params[:clock_event].has_key? 'clock_in(5i)'
    params[:clock_event].parse_time_select! :clock_out if params[:clock_event].has_key? 'clock_out(5i)'
  end

end

所以我相信我在 form_tag 中正确调用了路径,但是当我加载页面时出现错误:No route matches {:action=&gt;"show", :controller=&gt;"timecards"} 即使 timecards_controller 中有显示操作。

我认为我必须为显式 url 设置 form_tag 并以某种方式在参数中传递用户的 :id。但我对如何做到这一点有点坚持。

所以总结一下。当我有下拉菜单时,我选择一个用户,单击“查看员工”,这应该转到 timecards_controller.rb 中的显示操作,URL 为 /timecards/3(作为示例)。我以前从未以这种方式使用过 form_tag,所以传递路径或显式 url 对我来说有点陌生。

【问题讨论】:

  • 能否请您发布您的控制器的代码?
  • @ArslanAli 我已经更新了我的控制器代码,感谢您的关注!

标签: ruby-on-rails forms ruby-on-rails-3


【解决方案1】:

一个简单的修补程序:

最简单的解决方法是将表单更改为一堆链接。

<%= User.employee.order("username ASC").each |u| %>
  <%= link_to u.username, timecard_path %>
<% end %>

否则您可以使用 Javascript 来简单地使表单重定向:

<%= form_tag timecodes_path, :method => :get, :id => 'timecode_employee' do %>
  <%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username) %>
  <%= submit_tag "View Employee", class: "btn btn-primary" %>
<% end %>

$("#timecode_employee").submit(function(e){
  var form = $(this);
  // redirect to timecards/:id
  window.location = form.attr('action') + form.find('select').val();
  e.preventDefault();
});

重新设计的提示

您的设计可以通过添加一个基础的 TimeCard 模型得到根本性的改进。

这是一个非常常见的案例,可以告诉您原因:

客户决定他们希望经理准时签字 每月发卡。

哦,废话。现在我们需要获取该范围内的所有ClockEvents 并更新每个“clock_events.state”。

但是客户也想知道是谁在卡上签字。所以你添加一个clock_events.signed_off_by_id 并更新所有的时钟事件。然后他们希望三位经理签字,等等。

另一种设计

请注意,这是一个自以为是的通用示例。

class ClockEvent < ActiveRecord::Base
  enum status: [:clocked_in, :clocked_out]
  has_many :users
  belongs_to :time_card
end

class TimeCard < ActiveRecord::Base
  belongs_to :user
  has_many :clock_events
  accepts_nested_attributes_for :clock_events
end

class User < ActiveRecord::Base
  has_many :time_cards
  has_many :clock_events, through: :time_cards
end

TimeCard 可能会每月自动发放,或者如果您不想改变,而只是坚持为每个用户使用一张 TimeCard。 让我们在这里走一些传统路线:

resources :time_cards
end
resources :clock_events do
end
resources :users, shallow: true do
  resources :clock_events do
  end
  resources :time_cards do
  end
end

现在想象我们有一个经典的打卡时钟。

我们会打:

POST /clock_events { user_id: 1, time_card_id: 5 }

然后打出:

PATCH /clock_events/1 { status: :clocked_out }

这就是你的 REST。

内斯托普雷斯托

我们为每个用户的时间卡和时钟事件嵌套了路由:

GET /users/1/time_cards
GET /users/1/clock_events

从中,我们可以选择构建一个专用的UserTimeCardController,或者我们可以通过 TimeCardsController 中的用户 ID 参数来确定范围。

class TimeCardsController
  def index
    @time_cards = TimeCard.all
    @time_cards = @time_cards.where(user: params[:user_id]) if params[:user_id]
    @users = scope.all
  end
end

过滤

但是想象一下,如果我们希望经理能够过滤他在索引上看到的员工数量 - 一个好的架构应该是这样的:

class TimeCardsController
  def index
    @time_cards = TimeCard.all
    @time_cards = @time_cards.where(user: params[:user_id]) if params[:user_id] 
    if params[:filters]
       @time_cards = @time_cards.search(params[:search])
    end
  end
end

在我们的索引页面上,我们会添加这样的表单:

<%= form_tag(time_cards_path, method: :get) %>
  <%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username), multiple: true %>
  <%= submit_tag "Filter", class: "btn btn-primary" %>
<% end %>

【讨论】:

  • 注意,这是一个粗略的草图。为了保持已经很长的答案可读,我省略了一堆东西,比如授权以及如何在最后一步实际进行过滤。您应该查看 CanCanCan 或 Pundit 的授权和 Rolify 管理角色。但这超出了原始问题的范围。
  • 这太棒了。对于未来的设计模式,我一定会对此进行审查。不幸的是,我无法在今天下午 1 点之前将其赶出,所以我认为我没有时间重新设计。我正准备离开我的工作,所以我正在努力让事情变得平静,并且仍然使用我想出的复杂设计。不幸的是,我有 3 天的时间来完成这个功能,所以我有点吃力,并且在 form_tag 上挣扎,因为我打破了 restful/rails 约定。
  • 等一下,如果您的表单 url 中的 url 应该转到您有搜索功能的 timecards#index,那么它应该是 timecards_path
  • 这真是个好作品。如果我有更多时间,我会以这种方式重写整个功能。它是如此的干净和宁静,但我今天要发货:(
  • @maxcal 实际上,我决定使用修补程序。但是,您的 JS 已关闭,它正在重定向到 /timecards3,所以我在其中注入了一个 /,就像这样 window.location = form.attr('action') + '/' + form.find('select').val(); 就像一个魅力一样,完全符合我的需要。 :) 现在发货
【解决方案2】:

你定义的路线:

timecard GET    /timecards/:id(.:format)             timecards#show

需要id 才能显示正确的考勤卡。但是当您在form_tag 中调用它时,您只是发送timecard_path 而没有id。所以你确实需要发送idtimecard 对象,Rails 会自动从中提取id

所以,应该是:

form_tag @timecard do
  # other code
end

@timecard 必须在呈现部分的操作中实例化,并且它必须是有效的TimeCard 对象。

【讨论】:

  • 感谢您的回答,我了解该路线需要一个 id 才能显示正确的用户的考勤卡。我尝试了form_tag @timecard do,并在控制器中实例化了@timecard,就像@timecard = User.employee一样,我收到以下错误:undefined method model_name' for ActiveRecord::Relation:Class`
  • @cz3ch 听起来您已将 User 上的员工关系定义为 has_many。
  • @cz3ch @timecard 必须是从 Timecard 模型实例化的对象。我不知道User.employee 返回什么,或者你怎么能在课堂级别的User 上调用employee
  • User.employee 返回具有role 而不是medic 的所有用户的范围。 ` scope :employee, -> { where('role != ?', 'medic') } ` 这就是我在用户上调用员工的方式。实例化的问题是没有 TimeCard 模型。 TimeCards 只是一个控制器,让我看一下 ClockEvent 和 User 模型。所以是的,我在这里打破了 Rails 约定,但需要弄清楚如何让它工作。
  • 但在这种情况下,您实际上必须获取用户 timecard_path(User.employee.last) 的实例,但它的代码味道非常糟糕。
【解决方案3】:

我肯定会接受@maxcal 的建议并重写这件事,但由于我必须快速发布,我想出了一个“有效”的丑陋 UI hack。我不喜欢它,但它完全符合我的需要。

<ul class="dropdown">
  <a href="#" class="dropdown-toggle" data-toggle="dropdown">
    <button class="btn btn-medium btn-primary">View Employees</button>
    <b class="caret"></b>
  </a>
  <ul class="dropdown-menu">
    <% @users.each do |u| %>
      <li><%= link_to "#{u.username}", timecard_path(u) %>
    <% end %>
  </ul>
</ul>

我不喜欢所有的加载,这真的很脏,但在这种情况下,因为我打破了各种 Rails 约定并且必须在今天下午发货,所以它会一直工作,直到我能回来并让它变得更好。

让笑声开始吧。 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-28
    • 2018-03-19
    • 2015-10-16
    相关资源
    最近更新 更多