Home > OS >  Rails search based on from_date - to_date AND user.name
Rails search based on from_date - to_date AND user.name

Time:12-16

I am trying to implement a search function that search for matches in database, that matches a from_date and a to_date and also a user.name or user.id.

I have added the functionality to search for from_date - to_date, but I am at a loss on how I shall implement the user.name as well.

This is how I search for from_date - to_date

class ShiftSearch
    
    attr_reader :date_from, :date_to
    
    def initialize(params)
        params ||= {}
        @date_from = parsed_date(params[:date_from], 7.days.ago.to_date.strftime("%d/%m/%y").to_s)
        @date_to = parsed_date(params[:date_to], Date.today.strftime("%d/%m/%y").to_s)
    end

    def scope
        Shift.where('check_in_date BETWEEN ? AND ? or check_out_date BETWEEN ? AND ?', @date_from, @date_to, @date_from, @date_to)
    end

    
    private

    def parsed_date(date_string, default)
        return Date.parse(date_string).strftime("%y/%m/%d")
    rescue ArgumentError, TypeError
        default
    end
end

I have a controller that uses this class:

def shifts_search
  @search = ShiftSearch.new(params[:search])
  @shifts = @search.scope
end

And I also of course have a view to render the results:

<div >
  <%= form_tag admin_shifts_search_path, method: :get do %>
  <%= text_field_tag 'search[date_from]', @search.date_from %>
  <%= text_field_tag 'search[date_to]', @search.date_to %>
  <br>
  <%= submit_tag 'Søg',class: "btn btn-medium btn-white btn-outline-success mt-3" %>
  <% end %>
</div>
<hr>
<% if @shifts.any? %>
<table >
  <thead>
    <tr >
      <th scope="col" style="color:black;" >ID#</th>
      <th scope="col" style="color:black;" >NAVN</th>
      <th scope="col" style="color:black;" >IND_DAG</th>
      <th scope="col" style="color:black;" >IND_DATO</th>
      <th scope="col" style="color:black;" >IND_TID</th>
      <th scope="col" style="color:black;" >UD_DAG</th>
      <th scope="col" style="color:black;" >UD_DATO</th>
      <th scope="col" style="color:black;" >UD_TID</th>
      <th scope="col" style="color:black;" >TOTAL_TID TIMER:MINUTTER</th>
    </tr>
  </thead>
  <% @shifts.each do |s| %>
  <% if s.check_out_time == nil %>
  <tbody>
    <tr >
      <th scope="row" style="color:black;" >1</th>
      <td style="color:black;" ><%= s.worker.name %></td>
      <td style="color:black;" ><%= s.check_in_weekday %></td>
      <td style="color:black;" ><%= s.check_in_date %></td>
      <td style="color:black;" ><%= s.check_in_time %></td>
      <td style="color:black;" >Ikke afsluttet</td>
      <td style="color:black;" >Ikke afsluttet</td>
      <td style="color:black;" >Ikke afsluttet</td>
      <td style="color:black;" >Ikke afsluttet</td>
    </tr>
  </tbody>
  <% else %>
  <tbody>
    <tr >
      <td style="color:black;" ><%= s.id %></td>
      <td style="color:black;" ><%= s.worker.name %></td>
      <td style="color:black;" ><%= s.check_in_weekday %></td>
      <td style="color:black;" ><%= s.check_in_date %></td>
      <td style="color:black;" ><%= s.check_in_time %></td>
      <td style="color:black;" ><%= s.check_out_weekday %></td>
      <td style="color:black;" ><%= s.check_out_date %></td>
      <td style="color:black;" ><%= s.check_out_time %></td>
      <td style="color:black;" ><%= s.total_hours %></td>
    </tr>
  </tbody>
  <% end %>
  <%end%>
</table>
<% end %>

How would I go about implementing the from - to date - with the user also to match? I wanna be able to click a worker, which is a model I have, and I want the worker which I am currently viewing, to be able to filter from and to date, for this specific worker. The current date search I have, supports the from and to date, for ALL workers in my table, thus I want to be able to show for specific workers as well.

Thank you!

Regards.

CodePudding user response:

You might send the worker_id in params too when you click on a worker, then you can update your scope method as

def worker
  worker = Worker.find(params[:worker_id])
end

def scope
  Shift.joins(:worker).where('check_in_date BETWEEN ? AND ? or check_out_date BETWEEN ? AND ?', @date_from, @date_to, @date_from, @date_to).where(worker: worker)
end

You might want to read about joining here: https://guides.rubyonrails.org/active_record_querying.html#joins

CodePudding user response:

I would start by setting up a nested route to get shifts for a specific user:

resources :workers do
  resources :shifts, only: :index, module: :workers
end
module Workers
  class ShiftsController < ApplicationController
    before_action :set_worker

    # GET /workers/1/shifts
    def index
      @shifts = @worker.shifts
    end

    private 

    def set_worker
      @worker = Worker.find(params[:worker_id])
    end
  end
end

There is no search by date functionality yet - but we will get to that. You're on the right path with creating a separate object for handling searches but you might want to actually leverage Rails by creating a model thats not backed by a database table:

class ShiftSearch
  include ActiveModel::Model 
  include ActiveModel::Attributes 

  attribute :base_scope, default: ->{ Shift.all }
  attribute :date_from, :date 
  attribute :date_to, :date 

  def to_scope
    filters.inject(base_scope) do |scope, filter|
      scope.merge(filter)
    end
  end 

  private 

  def filters
    [].then do |array|
      array << filter_by_date if date_from.present? && date_to.present?
    end
  end

  def filter_by_date
    Shift.where(check_in_date: date_from..date_to)
         .or(Shift.where(check_out_date: date_from..date_to))
  end 
end

This gives you typecasting, attribute assignment, validations and more. Then lets create a form thats bound to a model:

# app/views/shift_search/_form.html.erb 
<%= form_with(model: model, url: url, method: :get) do |form| %>
  <div >
    <%= form.label :date_from %>
    <%= form.date_input :date_from %>
  </div>
  <div >
    <%= form.label :date_to %>
    <%= form.date_input :date_to %>
  </div>
  <div >
    <%= form.submit_tag 'Søg',
             class: "btn btn-medium btn-white btn-outline-success mt-3" %>
  </div>
<% end %>

By using a model binding the form will "play back" the user input. No one likes a form that goes blank after submission.

And then add the search functionality to the controller:

module Workers
  class ShiftsController
    before_action :set_worker

    # GET /workers/1/shifts
    def index
      @shift_search = ShiftSearch.new(search_parameters)
      @shifts = @shift_search.to_scope
    end

    private 

    def search_parameters
      params.fetch(:shift_search, {})
            .permit(:date_to, :date_from)
            .merge(base_scope: @worker.shifts)
    end

    def set_worker
      @worker = Worker.find(params[:worker_id])
    end
  end
end
# app/views/workers/shifts/index.html.erb
<%= render partial: 'shift_search/form', 
      model: @shift_search,
      url: workers_shifts_path
%>

# ... display the shifts here

You can resuse views between this and the search for all workers by using partials.

  • Related