Module: Dbviewer::ApplicationHelper
- Defined in:
- app/helpers/dbviewer/application_helper.rb
Instance Method Summary collapse
-
#active_nav_class(controller_name, action_name = nil) ⇒ Object
Helper method to determine if current controller and action match.
-
#code_block_bg_class ⇒ Object
Helper method for code blocks background that adapts to dark mode.
-
#column_type_from_info(column_name, columns) ⇒ Object
Extract column type from columns info.
-
#column_type_icon(column_type) ⇒ Object
Get appropriate icon for column data type.
-
#common_params(options = {}) ⇒ Object
Common parameters for pagination and filtering.
-
#current_table?(table_name) ⇒ Boolean
Determine if the current table should be active in the sidebar.
-
#dashboard_nav_class ⇒ Object
Helper for highlighting dashboard link.
-
#default_operator_for_column_type(column_type) ⇒ Object
Determine default operator based on column type.
-
#erd_nav_class ⇒ Object
Helper for highlighting ERD link.
- #format_cell_value(value) ⇒ Object
-
#format_table_name(table_name) ⇒ Object
Format table name for display - truncate if too long.
-
#get_database_manager ⇒ Object
Helper to access the database manager.
-
#has_timestamp_column?(table_name) ⇒ Boolean
Check if a table has a created_at column.
-
#logs_nav_class ⇒ Object
Helper for highlighting SQL Logs link.
-
#next_sort_direction(column_name, current_order_by, current_direction) ⇒ Object
Determine the next sort direction based on the current one.
-
#operator_options_for_column_type(column_type) ⇒ Object
Generate operator options based on column type.
-
#per_page_url_params(table_name) ⇒ Object
Generate URL parameters for per-page dropdown.
-
#render_action_cell(row_data, columns, metadata = nil) ⇒ Object
Render action buttons for a record.
-
#render_column_filter(form, column_name, columns, column_filters) ⇒ Object
Render complete filter input group for a column.
-
#render_column_filter_input(form, column_name, column_type, column_filters) ⇒ Object
Render column filter input based on column type.
-
#render_column_filters_row(form, records, columns, column_filters) ⇒ Object
Render the column filters row.
-
#render_operator_select(form, column_name, column_type, column_filters) ⇒ Object
Render operator select for column filter.
-
#render_pagination(table_name, current_page, total_pages, params = {}) ⇒ Object
Render pagination UI.
-
#render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Render a complete table header row with sortable columns.
-
#render_table_body(records, metadata) ⇒ Object
Render the entire table body with rows.
-
#render_table_cell(cell, column_name, metadata) ⇒ Object
Render a cell that may include a foreign key link.
-
#render_table_row(row, records, metadata) ⇒ Object
Render a table row with cells.
-
#sort_icon(column_name, current_order_by, current_direction) ⇒ Object
Returns a sort icon based on the current sort direction.
-
#sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Generate a sortable column header link.
-
#stat_card_bg_class ⇒ Object
Returns the appropriate background class for stat cards that adapts to dark mode.
-
#tables_nav_class ⇒ Object
Helper for highlighting tables link.
-
#theme_toggle_icon ⇒ Object
Returns the theme toggle icon based on the current theme.
-
#theme_toggle_label ⇒ Object
Returns the aria label for the theme toggle button.
-
#time_grouping_links(table_name, current_grouping) ⇒ Object
Render time grouping links.
Instance Method Details
#active_nav_class(controller_name, action_name = nil) ⇒ Object
Helper method to determine if current controller and action match
328 329 330 331 332 333 334 335 336 337 |
# File 'app/helpers/dbviewer/application_helper.rb', line 328 def active_nav_class(controller_name, action_name = nil) current_controller = params[:controller].split("/").last active = current_controller == controller_name if action_name.present? active = active && params[:action] == action_name end active ? "active" : "" end |
#code_block_bg_class ⇒ Object
Helper method for code blocks background that adapts to dark mode
289 290 291 |
# File 'app/helpers/dbviewer/application_helper.rb', line 289 def code_block_bg_class "sql-code-block" end |
#column_type_from_info(column_name, columns) ⇒ Object
Extract column type from columns info
18 19 20 21 22 23 |
# File 'app/helpers/dbviewer/application_helper.rb', line 18 def column_type_from_info(column_name, columns) return nil unless columns.present? column_info = columns.find { |c| c[:name].to_s == column_name.to_s } column_info ? column_info[:type].to_s.downcase : nil end |
#column_type_icon(column_type) ⇒ Object
Get appropriate icon for column data type
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'app/helpers/dbviewer/application_helper.rb', line 308 def column_type_icon(column_type) case column_type.to_s.downcase when /int/, /serial/, /number/, /decimal/, /float/, /double/ "bi-123" when /char/, /text/, /string/, /uuid/ "bi-fonts" when /date/, /time/ "bi-calendar" when /bool/ "bi-toggle-on" when /json/, /jsonb/ "bi-braces" when /array/ "bi-list-ol" else "bi-file-earmark" end end |
#common_params(options = {}) ⇒ Object
Common parameters for pagination and filtering
187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'app/helpers/dbviewer/application_helper.rb', line 187 def common_params( = {}) params = { order_by: @order_by, order_direction: @order_direction, per_page: @per_page, column_filters: @column_filters }.merge() # Add creation filters if they exist params[:creation_filter_start] = @creation_filter_start if @creation_filter_start.present? params[:creation_filter_end] = @creation_filter_end if @creation_filter_end.present? params end |
#current_table?(table_name) ⇒ Boolean
Determine if the current table should be active in the sidebar
294 295 296 |
# File 'app/helpers/dbviewer/application_helper.rb', line 294 def current_table?(table_name) @table_name.present? && @table_name == table_name end |
#dashboard_nav_class ⇒ Object
Helper for highlighting dashboard link
340 341 342 |
# File 'app/helpers/dbviewer/application_helper.rb', line 340 def dashboard_nav_class active_nav_class("home") end |
#default_operator_for_column_type(column_type) ⇒ Object
Determine default operator based on column type
26 27 28 29 30 31 32 |
# File 'app/helpers/dbviewer/application_helper.rb', line 26 def default_operator_for_column_type(column_type) if column_type && column_type =~ /char|text|string|uuid|enum/i "contains" else "eq" end end |
#erd_nav_class ⇒ Object
Helper for highlighting ERD link
350 351 352 |
# File 'app/helpers/dbviewer/application_helper.rb', line 350 def erd_nav_class active_nav_class("entity_relationship_diagrams") end |
#format_cell_value(value) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'app/helpers/dbviewer/application_helper.rb', line 159 def format_cell_value(value) return "NULL" if value.nil? return value.to_s.truncate(100) unless value.is_a?(String) case value when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ # ISO 8601 datetime begin Time.parse(value).strftime("%Y-%m-%d %H:%M:%S") rescue value.to_s.truncate(100) end when /\A\d{4}-\d{2}-\d{2}\z/ # Date value when /\A{.+}\z/, /\A\[.+\]\z/ # JSON begin JSON.pretty_generate(JSON.parse(value)).truncate(100) rescue value.to_s.truncate(100) end else value.to_s.truncate(100) end end |
#format_table_name(table_name) ⇒ Object
Format table name for display - truncate if too long
299 300 301 302 303 304 305 |
# File 'app/helpers/dbviewer/application_helper.rb', line 299 def format_table_name(table_name) if table_name.length > 20 "#{table_name.first(17)}..." else table_name end end |
#get_database_manager ⇒ Object
Helper to access the database manager
13 14 15 |
# File 'app/helpers/dbviewer/application_helper.rb', line 13 def get_database_manager @database_manager ||= ::Dbviewer::Database::Manager.new end |
#has_timestamp_column?(table_name) ⇒ Boolean
Check if a table has a created_at column
4 5 6 7 8 9 10 |
# File 'app/helpers/dbviewer/application_helper.rb', line 4 def (table_name) return false unless table_name.present? # Get the columns for the table directly using DatabaseManager columns = get_database_manager.table_columns(table_name) columns.any? { |col| col[:name] == "created_at" && [ :datetime, :timestamp ].include?(col[:type]) } end |
#logs_nav_class ⇒ Object
Helper for highlighting SQL Logs link
355 356 357 |
# File 'app/helpers/dbviewer/application_helper.rb', line 355 def logs_nav_class active_nav_class("logs") end |
#next_sort_direction(column_name, current_order_by, current_direction) ⇒ Object
Determine the next sort direction based on the current one
370 371 372 373 374 375 376 |
# File 'app/helpers/dbviewer/application_helper.rb', line 370 def next_sort_direction(column_name, current_order_by, current_direction) if column_name == current_order_by && current_direction == "ASC" "DESC" else "ASC" end end |
#operator_options_for_column_type(column_type) ⇒ Object
Generate operator options based on column type
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/helpers/dbviewer/application_helper.rb', line 35 def (column_type) # Common operators for all types common_operators = [ [ "is null", "is_null" ], [ "is not null", "is_not_null" ] ] type_specific_operators = if column_type && (column_type =~ /datetime/ || column_type =~ /^date$/ || column_type =~ /^time$/) # Date/Time operators [ [ "=", "eq" ], [ "≠", "neq" ], [ "<", "lt" ], [ ">", "gt" ], [ "≤", "lte" ], [ "≥", "gte" ] ] elsif column_type && column_type =~ /int|float|decimal|double|number|numeric|real|money|bigint|smallint|tinyint|mediumint|bit/i # Numeric operators [ [ "=", "eq" ], [ "≠", "neq" ], [ "<", "lt" ], [ ">", "gt" ], [ "≤", "lte" ], [ "≥", "gte" ] ] else # Text operators [ [ "contains", "contains" ], [ "not contains", "not_contains" ], [ "=", "eq" ], [ "≠", "neq" ], [ "starts with", "starts_with" ], [ "ends with", "ends_with" ] ] end # Return type-specific operators first, then common operators type_specific_operators + common_operators end |
#per_page_url_params(table_name) ⇒ Object
Generate URL parameters for per-page dropdown
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'app/helpers/dbviewer/application_helper.rb', line 234 def per_page_url_params(table_name) # Start with the dynamic part for the select element url_params = "per_page=' + this.value + '&page=1" # Add all other common parameters except per_page and page which we already set params = common_params.except(:per_page, :page) # Convert the params hash to URL parameters params.each do |key, value| if key == :column_filters && value.is_a?(Hash) && value.reject { |_, v| v.blank? }.any? value.reject { |_, v| v.blank? }.each do |filter_key, filter_value| url_params += "&column_filters[#{filter_key}]=#{CGI.escape(filter_value.to_s)}" end elsif value.present? url_params += "&#{key}=#{CGI.escape(value.to_s)}" end end url_params end |
#render_action_cell(row_data, columns, metadata = nil) ⇒ Object
Render action buttons for a record
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'app/helpers/dbviewer/application_helper.rb', line 499 def render_action_cell(row_data, columns, = nil) data_attributes = {} # Create a hash of column_name: value pairs for data attributes columns.each_with_index do |column_name, index| data_attributes[column_name] = row_data[index].to_s end content_tag(:td, class: "text-center action-column") do content_tag(:div, class: "d-flex gap-1 justify-content-center") do # View Record button (existing) = ( type: "button", class: "btn btn-sm btn-primary view-record-btn", title: "View Record Details", data: { bs_toggle: "modal", bs_target: "#recordDetailModal", record_data: data_attributes.to_json, foreign_keys: && [:foreign_keys] ? [:foreign_keys].to_json : "[]", reverse_foreign_keys: && [:reverse_foreign_keys] ? [:reverse_foreign_keys].to_json : "[]" } ) do content_tag(:i, "", class: "bi bi-eye") end # Copy FactoryBot button (new) = ( type: "button", class: "btn btn-sm btn-outline-secondary copy-factory-btn", title: "Copy to JSON", data: { record_data: data_attributes.to_json, table_name: @table_name }, onclick: "copyToJson(this)" ) do content_tag(:i, "", class: "bi bi-clipboard") end # Concatenate both buttons + end end end |
#render_column_filter(form, column_name, columns, column_filters) ⇒ Object
Render complete filter input group for a column
148 149 150 151 152 153 154 155 156 157 |
# File 'app/helpers/dbviewer/application_helper.rb', line 148 def render_column_filter(form, column_name, columns, column_filters) column_type = column_type_from_info(column_name, columns) content_tag(:div, class: "filter-input-group") do operator_select = render_operator_select(form, column_name, column_type, column_filters) input_field = render_column_filter_input(form, column_name, column_type, column_filters) operator_select + input_field end end |
#render_column_filter_input(form, column_name, column_type, column_filters) ⇒ Object
Render column filter input based on column type
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'app/helpers/dbviewer/application_helper.rb', line 79 def render_column_filter_input(form, column_name, column_type, column_filters) # Get selected operator to check if it's a null operator operator = column_filters["#{column_name}_operator"] is_null_operator = operator == "is_null" || operator == "is_not_null" # Clean up the value for non-null operators if the value contains a null operator # This ensures we don't carry over 'is_null' or 'is_not_null' values when switching operators value = column_filters[column_name] if !is_null_operator && value.present? && (value == "is_null" || value == "is_not_null") value = nil end # For null operators, display a non-editable field without placeholder if is_null_operator # Keep a hidden field for the actual value hidden_field = form.hidden_field("column_filters[#{column_name}]", value: operator, class: "null-filter-value", data: { column: column_name }) # Add a visible but disabled text field with no placeholder or value visible_field = form.text_field("column_filters[#{column_name}_display]", disabled: true, value: "", class: "form-control form-control-sm column-filter rounded-0 disabled-filter", data: { column: "#{column_name}_display" }) hidden_field + visible_field elsif column_type && column_type =~ /datetime/ form.datetime_local_field("column_filters[#{column_name}]", value: value, class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) elsif column_type && column_type =~ /^date$/ form.date_field("column_filters[#{column_name}]", value: value, class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) elsif column_type && column_type =~ /^time$/ form.time_field("column_filters[#{column_name}]", value: value, class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) else form.text_field("column_filters[#{column_name}]", value: value, placeholder: "", class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) end end |
#render_column_filters_row(form, records, columns, column_filters) ⇒ Object
Render the column filters row
428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'app/helpers/dbviewer/application_helper.rb', line 428 def render_column_filters_row(form, records, columns, column_filters) return content_tag(:tr) { content_tag(:th, "") } unless records&.columns content_tag(:tr, class: "column-filters") do filters = records.columns.map do |column_name| content_tag(:th, class: "p-0") do render_column_filter(form, column_name, columns, column_filters) end end filters.join.html_safe end end |
#render_operator_select(form, column_name, column_type, column_filters) ⇒ Object
Render operator select for column filter
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'app/helpers/dbviewer/application_helper.rb', line 132 def render_operator_select(form, column_name, column_type, column_filters) # Get previously selected operator or default default_operator = default_operator_for_column_type(column_type) selected_operator = column_filters["#{column_name}_operator"] selected_operator = default_operator if selected_operator.nil? || selected_operator == "default" # Get appropriate options = (column_type) form.select("column_filters[#{column_name}_operator]", (, selected_operator), { include_blank: false }, { class: "form-select form-select-sm operator-select" }) end |
#render_pagination(table_name, current_page, total_pages, params = {}) ⇒ Object
Render pagination UI
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/helpers/dbviewer/application_helper.rb', line 203 def render_pagination(table_name, current_page, total_pages, params = {}) return unless total_pages && total_pages > 1 content_tag(:nav, 'aria-label': "Page navigation") do content_tag(:ul, class: "pagination justify-content-center") do prev_link = content_tag(:li, class: "page-item #{current_page == 1 ? 'disabled' : ''}") do link_to "«", table_path(table_name, params.merge(page: [ current_page - 1, 1 ].max)), class: "page-link" end # Calculate page range to display start_page = [ 1, current_page - 2 ].max end_page = [ start_page + 4, total_pages ].min start_page = [ 1, end_page - 4 ].max # Generate page links page_links = (start_page..end_page).map do |page_num| content_tag(:li, class: "page-item #{page_num == current_page ? 'active' : ''}") do link_to page_num, table_path(table_name, params.merge(page: page_num)), class: "page-link" end end.join.html_safe next_link = content_tag(:li, class: "page-item #{current_page == total_pages ? 'disabled' : ''}") do link_to "»", table_path(table_name, params.merge(page: [ current_page + 1, total_pages ].min)), class: "page-link" end prev_link + page_links + next_link end end end |
#render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Render a complete table header row with sortable columns
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'app/helpers/dbviewer/application_helper.rb', line 404 def render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) return content_tag(:tr) { content_tag(:th, "No columns available") } unless records&.columns content_tag(:tr) do # Start with action column header (sticky first column) headers = [ content_tag(:th, class: "px-3 py-2 text-center action-column action-column-header", width: "60px", rowspan: 2) do content_tag(:span, "Actions") end ] # Add all data columns headers += records.columns.map do |column_name| is_sorted = order_by == column_name content_tag(:th, class: "px-3 py-2 sortable-column #{is_sorted ? 'sorted' : ''}") do sortable_column_header(column_name, order_by, order_direction, table_name, current_page, per_page, column_filters) end end headers.join.html_safe end end |
#render_table_body(records, metadata) ⇒ Object
Render the entire table body with rows
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'app/helpers/dbviewer/application_helper.rb', line 480 def render_table_body(records, ) if records.nil? || records.rows.nil? || records.empty? content_tag(:tbody) do content_tag(:tr) do # Adding +1 to account for the action column total_columns = records&.columns&.size.to_i + 1 content_tag(:td, "No records found or table is empty.", colspan: total_columns, class: "text-center") end end else content_tag(:tbody) do records.rows.map do |row| render_table_row(row, records, ) end.join.html_safe end end end |
#render_table_cell(cell, column_name, metadata) ⇒ Object
Render a cell that may include a foreign key link
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'app/helpers/dbviewer/application_helper.rb', line 443 def render_table_cell(cell, column_name, ) cell_value = format_cell_value(cell) foreign_key = && [:foreign_keys] ? [:foreign_keys].find { |fk| fk[:column] == column_name } : nil if foreign_key && !cell.nil? fk_params = { column_filters: { foreign_key[:primary_key] => cell } } fk_params = fk_params.merge(common_params.except(:column_filters)) content_tag(:td, title: "#{cell_value} (Click to view referenced record)") do link_to(cell_value, table_path(foreign_key[:to_table], fk_params), class: "text-decoration-none foreign-key-link") + content_tag(:i, "", class: "bi bi-link-45deg text-muted small") end else content_tag(:td, cell_value, title: cell_value) end end |
#render_table_row(row, records, metadata) ⇒ Object
Render a table row with cells
464 465 466 467 468 469 470 471 472 473 474 475 476 477 |
# File 'app/helpers/dbviewer/application_helper.rb', line 464 def render_table_row(row, records, ) content_tag(:tr) do # Start with action column (sticky first column) cells = [ render_action_cell(row, records.columns, ) ] # Add all data cells cells += row.each_with_index.map do |cell, cell_index| column_name = records.columns[cell_index] render_table_cell(cell, column_name, ) end cells.join.html_safe end end |
#sort_icon(column_name, current_order_by, current_direction) ⇒ Object
Returns a sort icon based on the current sort direction
360 361 362 363 364 365 366 367 |
# File 'app/helpers/dbviewer/application_helper.rb', line 360 def sort_icon(column_name, current_order_by, current_direction) if column_name == current_order_by direction = current_direction == "ASC" ? "up" : "down" "<i class='bi bi-sort-#{direction}'></i>".html_safe else "<i class='bi bi-filter invisible sort-icon'></i>".html_safe end end |
#sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Generate a sortable column header link
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'app/helpers/dbviewer/application_helper.rb', line 379 def sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) is_sorted = column_name == current_order_by sort_direction = next_sort_direction(column_name, current_order_by, current_direction) aria_sort = if is_sorted current_direction.downcase == "asc" ? "ascending" : "descending" else "none" end # Use common_params helper to build parameters sort_params = common_params(order_by: column_name, order_direction: sort_direction) link_to table_path(table_name, sort_params), class: "d-flex align-items-center text-decoration-none text-reset column-sort-link", title: "Sort by #{column_name} (#{sort_direction.downcase})", "aria-sort": aria_sort, role: "button", tabindex: "0" do content_tag(:span, column_name, class: "column-name") + content_tag(:span, sort_icon(column_name, current_order_by, current_direction), class: "sort-icon-container") end end |
#stat_card_bg_class ⇒ Object
Returns the appropriate background class for stat cards that adapts to dark mode
284 285 286 |
# File 'app/helpers/dbviewer/application_helper.rb', line 284 def stat_card_bg_class "stat-card-bg" end |
#tables_nav_class ⇒ Object
Helper for highlighting tables link
345 346 347 |
# File 'app/helpers/dbviewer/application_helper.rb', line 345 def tables_nav_class active_nav_class("tables") end |
#theme_toggle_icon ⇒ Object
Returns the theme toggle icon based on the current theme
274 275 276 |
# File 'app/helpers/dbviewer/application_helper.rb', line 274 def theme_toggle_icon '<i class="bi bi-moon"></i><i class="bi bi-sun"></i>'.html_safe end |
#theme_toggle_label ⇒ Object
Returns the aria label for the theme toggle button
279 280 281 |
# File 'app/helpers/dbviewer/application_helper.rb', line 279 def theme_toggle_label "Toggle dark mode" end |
#time_grouping_links(table_name, current_grouping) ⇒ Object
Render time grouping links
256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'app/helpers/dbviewer/application_helper.rb', line 256 def time_grouping_links(table_name, current_grouping) params = common_params content_tag(:div, class: "btn-group btn-group-sm", role: "group", 'aria-label': "Time grouping") do [ link_to("Hourly", table_path(table_name, params.merge(time_group: "hourly")), class: "btn btn-outline-primary #{current_grouping == 'hourly' ? 'active' : ''}"), link_to("Daily", table_path(table_name, params.merge(time_group: "daily")), class: "btn btn-outline-primary #{current_grouping == 'daily' ? 'active' : ''}"), link_to("Weekly", table_path(table_name, params.merge(time_group: "weekly")), class: "btn btn-outline-primary #{current_grouping == 'weekly' ? 'active' : ''}") ].join.html_safe end end |