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.