【问题标题】:Using scopes to filter results in rails 5使用范围过滤 Rails 5 中的结果
【发布时间】:2017-09-08 04:58:39
【问题描述】:

我遇到了 Rails 中的范围问题。目前,我在索引控制器中有一个 case 语句,它导致应用程序要么过滤 1 个类别 OR 另一个类别。 但我需要能够同时过滤多个内容。基本上是链接范围,因此当用户单击链接 A 时,将应用范围 A,然后当他们单击链接 B 时,将范围 B 添加到范围 A 的顶部以进行更多过滤。最终我想在视图中添加一个复选框,以便在选中时应用范围,而在取消选中时删除范围。

我现在使用一堆条件逻辑 (if\else) 语句,因为我不仅需要过滤说“经验”,还需要过滤 4 种不同的经验选项(0-2 年, 2-5 年、5-10 年等……)。这对于所有范围都是相同的。

因此,总而言之,我需要找到一种链接范围的方法,以便继续应用过滤器(过滤器 A + 过滤器 B + 过滤器 C 等),而不是像现在这样使用它们A 或过滤器 B,但无法让它们一起工作以进一步缩小结果范围。

这是我的范围

scope :by_experience, -> (ex) { where(experience: ex) }

scope :by_num_days_past, -> (days_past) { where('created_at >= ?', days_past.days.ago) }

这是我的看法

            <!-- toggle for experience -->  
            <a class="" role="" data-toggle="collapse" 
href="#experience-collapse" aria-expanded="false" aria-
controls="experience-collapse">
                Experience<br />
            </a>
            <div class="collapse" id="experience-collapse">
              <div class="well">
                <%= link_to "0-2 years", filtered_jobs_path(:experience, '0-2 years') %><br />
                <%= link_to "2-5 years", filtered_jobs_path(:experience, '2-5 years') %><br />
                <%= link_to "5-10 years", filtered_jobs_path(:experience, '5-10 years') %><br />
                <%= link_to "10+ years", filtered_jobs_path(:experience, '10+ years') %>        
              </div>
            </div>  
            <br />            

   

            <!-- toggle for num_days_past -->  
            <a class="" role="" data-toggle="collapse" href="#num-days-
past-collapse" aria-expanded="false" aria-controls="num-days-past-
collapse">
                Days Listed<br />
            </a>
            <div class="collapse" id="num-days-past-collapse">
              <div class="well">
                <%= link_to "7 days", filtered_jobs_path(:num_days_past, '7') %><br />
                <%= link_to "30 days", filtered_jobs_path(:num_days_past, '30') %><br />
                <%= link_to "60 days", filtered_jobs_path(:num_days_past, '60') %><br />
              </div>
            </div>  
            <br />   

这是我的控制器的索引操作,我认为需要进行更改才能一次应用多个范围。

def index

      case params[:scope]  
      when "num_days_past"
        if (params[:format] == "7")
          @jobs = Job.by_num_days_past("7").paginate(page: params[:page], per_page: 5)     
        elsif (params[:format] == "30")
          @jobs = Job.by_num_days_past("30").paginate(page: params[:page], per_page: 5)     
        elsif (params[:format] == "60")
          @jobs = Job.by_num_days_past("60").paginate(page: params[:page], per_page: 5)  
         else   
             @jobs = Job.order('created_at DESC').paginate(page: params[:page], per_page: 5)        
         end      
end

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-5


    【解决方案1】:

    在实例变量上调用多个作用域会使它们链接起来。请尝试以下操作:

    def index
    
        @jobs = Job.by_num_days_past("7") if (params[:format] == "7")     
        @jobs = @jobs&.by_num_days_past("30") || Job.by_num_days_past("30") if (params[:format] == "30")        
        @jobs = @jobs&.by_num_days_past("60") || Job.by_num_days_past("60") if (params[:format] == "60")         
        @jobs = Job.order('created_at DESC') if @jobs.blank?
        @jobs.paginate(page: params[:page], per_page: 5)
    
    end
    

    【讨论】:

      【解决方案2】:

      您可以延迟分页,直到您完成应用范围。像这样:

      def index
        case params[:scope]  
        when "num_days_past"
          if (params[:format] == "7")
            @jobs = Job.by_num_days_past("7")
          elsif (params[:format] == "30")
            @jobs = Job.by_num_days_past("30")
          elsif (params[:format] == "60")
            @jobs = Job.by_num_days_past("60")
          else
            @jobs = Job.all
          end 
      
        # Now you can apply scope 2
        # For example
        @jobs = @jobs.by_experience(params[:exp]) if params[:scope_by_experience]
      
        @jobs = @jobs.order('created_at DESC').paginate(page: params[:page], per_page: 5)        
      end
      

      #paginate 实现可能会立即执行查询,结果不会是您可以应用更多范围的 Active Record 对象。

      【讨论】:

        【解决方案3】:

        问题:

        它目前不起作用的原因是您没有保存当前的过滤器/范围。如果您单击第二个过滤器,您将不知道先前呈现的范围。

        要解决此问题,您需要将过滤器存储在客户端上,并在下一次请求时将其作为参数发送。这通常在 URL 中完成,这样您就可以使用过滤器共享网站。如果其他人访问该链接,过滤器会自动跳转到正确的位置。但也可以通过 cookie 来完成。

        例如看看:

        我知道它们对您来说可能是外语(荷兰语),但在打开和关闭过滤器时请仔细查看 URL 栏。过滤器被保存并在您发出的下一个请求中传递。

        一个可能的解决方案:

        目前您为过滤器创建了一个专用的路由,我的猜测是:

        resources :jobs do
          get '/:scope/:format', on: :collection, as: :filtered, action: :index
        end
        

        你应该做的是使用jobs_path 和这样的参数(view):

        jobs_path(number_of_days_past: 7, experience: '0-2 years')
        

        这将生成 URL:

        /jobs?number_of_days_past=7&experience=0-2+years
        

        如果您想将新过滤器与以前的过滤器结合起来,您必须先提取以前的过滤器。这是在 controller 中完成的,如下所示:

        def index
          @filters = params.permit(:number_of_days_past, :experience).to_h
        end
        

        创建一个辅助方法,用于将 @filters 与您要应用或删除的过滤器 (app/helpers/jobs_helper.rb) 结合起来。下面的示例可以打开和关闭过滤器,并支持多个过滤器,但不是给定过滤器的多个值。如果您希望每个过滤器支持多个值,请以此为例并根据自己的喜好对其进行编辑。

        def combine_filter(apply_filters)
          apply_filters.each_with_object(@filters.deep_dup) do |(key, value), filters|
            filters[key] == value ? filters.delete(key) : filters[key] = value
          end
        end
        
        # OR, if the above did go to fast.
        
        def combine_filter(apply_filters)
          # duplicate the @filters hash, this way multiple calls don't influence each other
          filters = @filters.deep_dup
          # loop through apply_filters (Hash) and check every filter
          apply_filters.each do |key, value|
            if filters[key] == value
              # filter content was the same, toggle it off
              filters.delete(key)
            else
              # filter content was not the same, replace value for this filter
              filters[key] = value 
            end
          end
          # return the filters variable
          filters
        end
        

        这会给你留下这样的视图

        <div class="well">
            <%= link_to "0-2 years", jobs_path(combine_filter(experience: '0-2 years')) %><br />
            <%= link_to "2-5 years", jobs_path(combine_filter(experience: '2-5 years')) %><br />
            <%= link_to "5-10 years", jobs_path(combine_filter(experience: '5-10 years')) %><br />
            <%= link_to "10+ years", jobs_path(combine_filter(experience: '10+ years')) %>        
        </div>
        

        此时,当在控制器中接收请求时,您在变量 @filters 中拥有所有过滤器,但仍需要处理它们。查看下面的在线资源以获取示例。

        在线帮助和资源:

        这是一篇关于如何实现简单版本的博文(处理传入的过滤器并调用正确的范围):

        但您也可以尝试为这项工作找到合适的宝石,这是我通过Google search 快​​速找到的:

        【讨论】:

          【解决方案4】:

          所以我最终使用了几个答案的组合。感谢 Oscar 解释将分页推迟到最后,Ahmand 感谢有关链接的信息,Johan 感谢解释有关 jobs_path 参数的信息。我会投票,但它说我还没有足够的代表点数。在这里发布我的答案,以防将来有人偶然发现。

          在控制器中,我从 Job.all 开始,然后在此之上构建范围,最后应用分页。我觉得我应该将所有这些移到模型中的一个方法中,然后从控制器中调用它。但它的工作方式是这样的,我稍后会测试将它移动到模型中。

          def index
            @jobs = Job.all
          
            # scopes
            if params[:experience].present? 
              @jobs = @jobs.by_experience(params[:experience])
            end
          
          
            if params[:num_days_past].present? 
              @jobs = @jobs.by_num_days_past(params[:num_days_past].to_i)
            end      
          
          
            @jobs = @jobs.paginate(page: params[:page], per_page: 5)
          end
          

          在我看来,我已将体验参数添加到链接中,这样如果参数存在,url 将显示体验和按天过滤。

                <div class="well">
                      <%= link_to "7 days", filtered_jobs_path(num_days_past: '7', experience: params[:experience]) %><br />
                      <%= link_to "30 days", filtered_jobs_path(num_days_past: '30', experience: params[:experience]) %><br />
                      <%= link_to "60 days", filtered_jobs_path(num_days_past: '60', experience: params[:experience]) %><br />
                </div>
          

          这是我的带有示波器的模型

           # scopes to filter results from database.
          scope :by_created_at, -> { order(created_at: :desc) }
          scope :by_experience, -> (ex) { where(experience: ex) if ex.present? }
          scope :by_num_days_past, -> (days_past) { where('created_at >= ?', days_past.days.ago) if days_past.present? }
          scope :by_location, -> (location) { where('city LIKE ? OR state LIKE ?', "%#{location}%", "%#{location}%") }
          

          这是我的路线,我删除了我之前拥有的 /filer/:scope。

            #get 'jobs/filter/:scope' => 'jobs#index', as: :filtered_jobs
            get 'jobs' => 'jobs#index', as: :filtered_jobs
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-03-03
            • 2015-09-05
            • 1970-01-01
            • 2017-06-03
            • 2013-08-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多