Skip to content

Instantly share code, notes, and snippets.

@julianrubisch
Created February 22, 2021 15:58
Show Gist options
  • Save julianrubisch/8d86e1f11b1ea1f2dfed51e0a21d157d to your computer and use it in GitHub Desktop.
Save julianrubisch/8d86e1f11b1ea1f2dfed51e0a21d157d to your computer and use it in GitHub Desktop.
# app/filters/base_filter.rb
class BaseFilter
include ActiveModel::Model
include ActiveModel::Attributes
def initialize(session)
@_session = session
super(@_session.fetch(:filters, {})[filter_resource_class])
end
def apply!(_chain)
raise NotImplementedError
end
def active_for?(attribute, value=true)
filter_attribute = send(attribute)
return filter_attribute.include?(value) if filter_attribute.is_a?(Enumerable)
filter_attribute == value
end
def filter_resource_class
@filter_resource_class || self.class.name.match(/\A(?<resource>.*)Filter\Z/)[:resource]
end
end
# app/filters/base_filter.rb
class BaseFilter
# ...
def merge!(_attribute, _value)
@_session[:filters] ||= {}
@_session[:filters][filter_resource_class] ||= {}
end
end
# app/filters/embed_filter.rb
class EmbedFilter < BaseFilter
# ...
def merge!(attribute, value)
super
if send(attribute).is_a? Array
if send(attribute).include? value
send(attribute).delete value
else
send(attribute) << value
end
end
@_session[:filters]["Embed"].merge!(attribute => send(attribute))
end
end
# app/filters/board_filter.rb
class BoardFilter < BaseFilter
attribute :query, :string, default: ""
def apply!(chain)
chain = chain.search(query) if query.present?
chain
end
def merge!(attribute, value)
super
send(:"#{attribute}=", value)
@_session[:filters]["Board"].merge!(attribute => send(attribute))
end
end
# app/filters/history_entry_filter.rb
class HistoryEntryFilter < BaseFilter
attribute :show_all, :boolean, default: false
def apply!(chain)
chain = chain.with_reactions unless show_all
chain
end
def merge!(attribute, value)
super
send(:"#{attribute}=", !send(attribute))
@_session[:filters]["HistoryEntry"].merge!(attribute => send(attribute))
end
end
# app/controllers/boards_controller.rb
class BoardsController
# ...
def show
@embed = @board.embeds.build
@embed_import = EmbedImport.new(board: @board)
@embeds = filter_for("Embed").apply!(@board.embeds)
end
# ...
end
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
include Filterable
def index
set_filter_for!("Board", "query", "")
@boards = current_user.boards.order(:created_at)
@boards = @boards.search(filter_for("Board", "query")) if filter_for("Board", "query").present?
end
def show
set_filter_for!("Embed", "embeddable_type", %w[MediaAsset OEmbedLink])
@embed = @board.embeds.build
@embed_import = EmbedImport.new(board: @board)
@embeds = @board.embeds.where(session[:filters]["Embed"])
end
# ...
end
# app/controllers/embed_controller.rb
class EmbedsController < ApplicationController
include Filterable
# ...
def show
set_filter_for!("HistoryEntry", "show_all", false)
@comment ||= Comment.new(user: current_user)
@comment.build_history_entry(embed: @embed)
@history_entries = @embed.history_entries.includes(:entryable)
@history_entries = @history_entries.with_reactions unless filter_for("HistoryEntry", "show_all")
end
# ...
end
# app/reflexes/filter_reflex.rb
class FilterReflex < ApplicationReflex
include Filterable
def filter
resource, param = element.dataset.to_h.fetch_values(:resource, :param)
value = element.dataset.value || element.value
if element["role"] == "checkbox"
if filter_for(resource, param).is_a? Array
if filter_for(resource, param).include? value
filter_for(resource, param).delete value
else
filter_for(resource, param) << value
end
else
set_filter_for!(resource, param, !filter_for(resource, param))
end
else
set_filter_for!(resource, param, value)
end
end
end
# app/filters/embed_filter.rb
class EmbedFilter < BaseFilter
attribute :embeddable_type, default: %w[MediaAsset OEmbedLink] # cannot use type: :array here :-(
def apply!(chain)
chain.where(embeddable_type: embeddable_type) if embeddable_type.present?
chain
end
end
def filter_active_for?(resource, param, value=true)
filter = session.fetch(:filters, {}).fetch(resource, {}).fetch(param, [])
return filter.include?(value) if filter.is_a?(Enumerable)
filter == value
end
<!-- app/views/boards/index.html.erb -->
<%= text_field_tag "Search",
session[:filters]["Board"]["title"],
placeholder: "Search for a board",
data: {reflex: "debounced:input->Filter#filter", resource: "Board", param: "query"},
%>
<button type="button"
role="checkbox"
data-reflex="click->Filter#filter"
data-resource="HistoryEntry"
data-param="show_all"
>
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="<%= filter_active_for?("HistoryEntry", "show_all") ? "translate-x-5" : "translate-x-0" %> ..."></span>
</button>
<div class="flex items-center">
<button type="button"
role="checkbox"
data-reflex="click->Filter#filter"
data-resource="Embed"
data-param="embeddable_type"
data-value="OEmbedLink"
>
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="<%= filter_active_for?("Embed", "embeddable_type", "OEmbedLink") ? "translate-x-5" : "translate-x-0" %> ..."></span>
</button>
<span class="ml-3" id="annual-billing-label">
<span class="text-sm font-medium text-gray-900">Show Links</span>
</span>
</div>
<div class="flex items-center">
<button type="button"
role="checkbox"
data-reflex="click->Filter#filter"
data-resource="Embed"
data-param="embeddable_type"
data-value="MediaAsset"
>
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="<%= filter_active_for?("Embed", "embeddable_type", "MediaAsset") ? "translate-x-5" : "translate-x-0" %> ..."></span>
</button>
<span class="ml-3" id="annual-billing-label">
<span class="text-sm font-medium text-gray-900">Show Uploads</span>
</span>
</div>
# app/reflexes/filter_reflex.rb
class FilterReflex < ApplicationReflex
include Filterable
def filter
resource, param = element.dataset.to_h.fetch_values(:resource, :param)
value = element.dataset.value || element.value
set_filter_for!(resource, param, value)
end
end
# app/reflexes/filter_reflex.rb
class FilterReflex < ApplicationReflex
def filter
resource, param = element.dataset.to_h.fetch_values(:resource, :param)
value = element.dataset.value || element.value
if element["role"] == "checkbox"
# checkbox toggles elements in an array on and off
# %w[OEmbedLink MediaAsset]
if session[:filters][resource][param].is_a? Array
if session[:filters][resource][param].include? value
session[:filters][resource][param].delete value
else
session[:filters][resource][param] << value
end
# checkbox toggles a single value on and off
else
session[:filters][resource][param] = !session[:filters][resource][param]
end
else
session[:filters][resource][param] = value
end
end
end
# app/controllers/concerns/filterable.rb
module Filterable
# ...
def filter_active_for?(resource, attribute, value=true)
filter = filter_for(resource)
filter.active_for?(attribute, value)
end
private
def filter_for(resource)
"#{resource}Filter".constantize.new(session)
end
# ...
end
module Filterable
extend ActiveSupport::Concern
private
def filter_for(resource, param)
session.fetch(:filters, {}).fetch(resource, {}).fetch(param, [])
end
def set_filter_for!(resource, param, value)
session[:filters] ||= {}
session[:filters][resource] ||= {}
session[:filters][resource][param] = value if (self.class.ancestors.include? StimulusReflex::Reflex || filter_for(resource, param).blank?)
end
end
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
# ...
def index
session[:filters] ||= {}
session[:filters]["Board"] ||= {"query" => ""}
query = session[:filters]["Board"]["query"]
@boards = current_user.boards.order(:created_at)
@boards = @boards.search(query) if query.present?
end
def show
session[:filters] ||= {}
session[:filters]["Embed"] ||= {"embeddable_type" => %w[MediaAsset OEmbedLink]}
@embed = @board.embeds.build
@embed_import = EmbedImport.new(board: @board)
@embeds = @board.embeds.where(session[:filters]["Embed"])
end
# ...
end
# app/controllers/embed_controller.rb
class EmbedsController < ApplicationController
# ...
def show
session[:filters] ||= {}
session[:filters]["HistoryEntry"] ||= {"show_all" => false}
@comment ||= Comment.new(user: current_user)
@comment.build_history_entry(embed: @embed)
@history_entries = @embed.history_entries.includes(:entryable)
@history_entries = @history_entries.with_reactions unless filter_for("HistoryEntry", "show_all")
end
# ...
end
# db/migrate/20210208120653_create_media_assets.rb
class CreateMediaAssets < ActiveRecord::Migration[6.1]
def change
create_table :media_assets do |t|
t.string :title
t.timestamps
end
end
end
# app/models/media_asset.rb
class MediaAsset < ApplicationRecord
include Embeddable
validates :title, presence: true
has_one_attached :media_file
def embed_code
"<audio controls><source src=\"#{Rails.application.routes.url_helpers.rails_blob_path(media_file, only_path: true)}\" type=\"audio/mp3\"></audio>".html_safe
end
end
# app/models/embed.rb
class Embed < ApplicationRecord
# ...
# 👇 add MediaAsset to the list of embeddable types
delegated_type :embeddable, types: %w(OEmbedLink MediaAsset)
# 👇 initialize MediaAsset correctly
after_initialize do
next if persisted?
if input&.start_with? "http"
self.embeddable = OEmbedLink.new(url: input)
else
self.embeddable = MediaAsset.new(title: input)
end
end
def title
self[:title] || embeddable.title
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment