binxtils 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1595e2d8526fd86fa68cd648e318960c951a046c7a5401bb3819a01f91f9d915
4
- data.tar.gz: a57ad2f943c81ccbc8245302639805b8ffa36529b8737871a5afdd143c162462
3
+ metadata.gz: e7eb389fe8adfebf07d5c4b775a457a89ffe7cedee03b4c635865ee844b5c237
4
+ data.tar.gz: b05037ae696c23642e45e2d04003e2ba4b76eb3b974ad6157388355aab41bf7c
5
5
  SHA512:
6
- metadata.gz: a7e95c3ad3df8c0a64ea582344c40033407e49e4844284b964bc21159b1933bfe121f1848b9c8f53913c496140c4a174af847d113527a63f8c1302b9f5040788
7
- data.tar.gz: 2cdd46b2ea2b0f9c45aa7d2432f7296210d481405d0154edea6ad1d9f3d18604059f27751225b11d73988dc9a0bc57c68a2db48f6e57e9a8a5b962faf5678868
6
+ metadata.gz: 1b3874b3af0782086081d84a32ec0c303abbab56775621ceb70f5366ffdfd5436cccfacd559e8bfc5f96894f2ed84a3e381c7f81a9f7991cea7914388039e995
7
+ data.tar.gz: fd12bc0f7d66071963d0bd52a763e27a6cccbddfab58174254031c588e60a8e2003907c2ee343a182a3e91d28acf91590c4dda05bbad55ea4bec8770bdf9f419
data/README.md CHANGED
@@ -12,12 +12,65 @@ gem "binxtils"
12
12
  - **Binxtils::TimeParser** - Parse fuzzy time/date strings into `Time` objects
13
13
  - **Binxtils::TimeZoneParser** - Parse and resolve time zone strings
14
14
 
15
- ## Usage
15
+ ### Functionable modules
16
16
 
17
- All modules use [Functionable](https://github.com/sethherr/functionable) and are called as class methods:
17
+ These modules use [Functionable](https://github.com/sethherr/functionable) and are called as class methods:
18
18
 
19
19
  ```ruby
20
20
  Binxtils::TimeParser.parse("next thursday")
21
21
  Binxtils::InputNormalizer.string(" Some Input ")
22
22
  Binxtils::TimeZoneParser.parse("Eastern Time")
23
23
  ```
24
+
25
+ ### Rails concerns
26
+
27
+ - **Binxtils::SetPeriod** - Controller concern for time period filtering (hour, day, week, month, year, all, custom). Parses period params, manages timezones, and sets `@time_range`.
28
+ - **Binxtils::SortableTable** - Controller concern providing `sort_column` and `sort_direction` helpers with configurable defaults.
29
+ - **Binxtils::SortableHelper** - View helper for rendering sortable column header links with active-state indicators.
30
+
31
+ Include them in your controllers and helpers:
32
+
33
+ ```ruby
34
+ class ApplicationController < ActionController::Base
35
+ include Binxtils::SetPeriod
36
+ include Binxtils::SortableTable
37
+
38
+ # Optionally configure the earliest date for the "all" period
39
+ self.default_earliest_time = Time.at(1714460400).freeze
40
+ end
41
+
42
+ module ApplicationHelper
43
+ include Binxtils::SortableHelper
44
+
45
+ # Optionally extend the permitted search params
46
+ def default_search_keys
47
+ super + [:organization_id, query_items: []]
48
+ end
49
+ end
50
+ ```
51
+
52
+ `SortableTable` requires a `sortable_columns` method in your controller:
53
+
54
+ ```ruby
55
+ class BikesController < ApplicationController
56
+ def sortable_columns
57
+ %w[created_at updated_at manufacturer_id]
58
+ end
59
+ end
60
+ ```
61
+
62
+ ## npm package
63
+
64
+ This repo also publishes `@bikeindex/time-localizer`, an npm package for localizing time elements in the browser. Luxon is bundled into the published package, so consumers don't need to install it separately.
65
+
66
+ To publish a new version: update the version in `package.json`, then run `npm publish` from the repo root (requires npm login with access to the `@bikeindex` scope). The `prepublishOnly` script automatically builds `dist/index.js` before publishing.
67
+
68
+ ## Releasing
69
+
70
+ From the `main` branch, run `bin/release` with a version number:
71
+
72
+ ```
73
+ bin/release 0.3.0
74
+ ```
75
+
76
+ This bumps the version, commits, tags, pushes, builds and publishes the gem to RubyGems, and creates a GitHub release.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binxtils
4
+ module SetPeriod
5
+ extend ActiveSupport::Concern
6
+
7
+ PERIOD_TYPES = %w[hour day month year week all next_week next_month].freeze
8
+
9
+ included do
10
+ class_attribute :default_earliest_time, default: Time.at(0).freeze
11
+ end
12
+
13
+ # For setting periods, particularly for graphing
14
+ def set_period
15
+ set_timezone
16
+ # Set time period
17
+ @period ||= params[:period]
18
+ if @period == "custom"
19
+ if params[:start_time].present?
20
+ @start_time = Binxtils::TimeParser.parse(params[:start_time], @timezone)
21
+ @end_time = Binxtils::TimeParser.parse(params[:end_time], @timezone) || latest_period_date
22
+ @start_time, @end_time = @end_time, @start_time if @start_time > @end_time
23
+ else
24
+ set_time_range_from_period
25
+ end
26
+ elsif params[:search_at].present?
27
+ @period = "custom"
28
+ @search_at = Binxtils::TimeParser.parse(params[:search_at], @timezone)
29
+ offset = params[:period].present? ? params[:period].to_i : 10.minutes.to_i
30
+ @start_time = @search_at - offset
31
+ @end_time = @search_at + offset
32
+ else
33
+ set_time_range_from_period
34
+ end
35
+ # Add this render_chart in here so we don't have to define it in all the controllers
36
+ @render_chart = Binxtils::InputNormalizer.boolean(params[:render_chart])
37
+ @time_range = @start_time..@end_time
38
+ end
39
+
40
+ private
41
+
42
+ def set_time_range_from_period
43
+ @period = default_period unless PERIOD_TYPES.include?(@period)
44
+
45
+ case @period
46
+ when "hour"
47
+ @start_time = Time.current - 1.hour
48
+ when "day"
49
+ @start_time = Time.current.beginning_of_day - 1.day
50
+ when "month"
51
+ @start_time = Time.current.beginning_of_day - 30.days
52
+ when "year"
53
+ @start_time = Time.current.beginning_of_day - 1.year
54
+ when "week"
55
+ @start_time = Time.current.beginning_of_day - 1.week
56
+ when "next_month"
57
+ @start_time ||= Time.current
58
+ @end_time = Time.current.beginning_of_day + 30.days
59
+ when "next_week"
60
+ @start_time = Time.current
61
+ @end_time = Time.current.beginning_of_day + 1.week
62
+ when "all"
63
+ @start_time = earliest_period_date
64
+ end
65
+ @end_time ||= latest_period_date
66
+ end
67
+
68
+ # Separate method so it can be overridden on per controller basis
69
+ def default_period
70
+ "all"
71
+ end
72
+
73
+ # Separate method so it can be overridden, specifically in invoices
74
+ def latest_period_date
75
+ Time.current
76
+ end
77
+
78
+ # Separate method so it can be overridden on per controller basis
79
+ def earliest_period_date
80
+ default_earliest_time
81
+ end
82
+
83
+ def set_timezone
84
+ return true if @timezone.present?
85
+
86
+ if params[:timezone].present?
87
+ @timezone = Binxtils::TimeZoneParser.parse(params[:timezone])
88
+ session[:timezone] = @timezone&.name
89
+ end
90
+
91
+ if session[:timezone].present?
92
+ @timezone ||= Binxtils::TimeZoneParser.parse(session[:timezone])
93
+ Time.zone = @timezone
94
+ end
95
+
96
+ @timezone ||= Binxtils::TimeParser.default_time_zone
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binxtils
4
+ module SortableHelper
5
+ BASE_SEARCH_KEYS = [
6
+ :direction, :sort, # sorting params
7
+ :period, :start_time, :end_time, :render_chart, # Time period params
8
+ :user_id, :query, :per_page # General search params
9
+ ].freeze
10
+
11
+ # Set defaults, required for testing
12
+ def sort_column = "id"
13
+ def sort_direction = "desc"
14
+
15
+ def default_search_keys
16
+ BASE_SEARCH_KEYS
17
+ end
18
+
19
+ def sortable(column, title = nil, html_options = {}, &block)
20
+ if title.is_a?(Hash) # If title is a hash, it wasn't passed
21
+ html_options = title
22
+ title = nil
23
+ end
24
+ title ||= column.gsub(/_(id|at)\z/, "").titleize
25
+
26
+ # Check for render_sortable - otherwise default to rendering
27
+ render_sortable = html_options.key?(:render_sortable) ? html_options[:render_sortable] : !html_options[:skip_sortable]
28
+ return title unless render_sortable
29
+
30
+ html_options[:class] = "#{html_options[:class]} sortable-link"
31
+ direction = (column == sort_column && sort_direction == "desc") ? "asc" : "desc"
32
+
33
+ if column == sort_column
34
+ html_options[:class] += " active"
35
+ span_content = (direction == "asc") ? "\u2193" : "\u2191"
36
+ end
37
+
38
+ link_to(sortable_url(column, direction), html_options) do
39
+ concat(block_given? ? capture(&block) : title.html_safe)
40
+ concat(content_tag(:span, span_content, class: "sortable-direction"))
41
+ end
42
+ end
43
+
44
+ def sortable_search_params?(except: [])
45
+ except_keys = %i[direction sort period per_page] + except
46
+ s_params = sortable_search_params.except(*except_keys).values.reject(&:blank?).any?
47
+
48
+ return true if s_params
49
+ return false if except.map(&:to_s).include?("period")
50
+
51
+ params[:period].present? && params[:period] != "all"
52
+ end
53
+
54
+ def sortable_params
55
+ @sortable_params ||= sortable_search_params.as_json.filter_map do |k, v|
56
+ next if v.blank? || k == "sort" && v == default_column ||
57
+ k == "direction" && v == default_direction
58
+ [k, v]
59
+ end.to_h.with_indifferent_access
60
+ end
61
+
62
+ def sortable_search_params
63
+ return @sortable_search_params if defined?(@sortable_search_params)
64
+
65
+ search_param_keys = params.keys.select { |k| k.to_s.start_with?("search_") } # match params starting with search_
66
+ @sortable_search_params = params.permit(*(default_search_keys | search_param_keys))
67
+ end
68
+
69
+ private
70
+
71
+ # This is a separate method purely for testing purposes, so it can be stubbed
72
+ def sortable_url(sort, direction)
73
+ url_for(sortable_search_params.merge(sort:, direction:))
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Binxtils
4
+ module SortableTable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :set_period, only: %i[index]
9
+
10
+ helper_method :sort_column, :sort_direction, :default_column, :default_direction
11
+ end
12
+
13
+ def sort_column
14
+ @sort_column ||= sortable_columns.include?(params[:sort]) ? params[:sort] : default_column
15
+ end
16
+
17
+ def sort_direction
18
+ %w[asc desc].include?(params[:direction]) ? params[:direction] : default_direction
19
+ end
20
+
21
+ def permitted_time_range_columns
22
+ %w[created_at updated_at].freeze
23
+ end
24
+
25
+ def current_time_range_column
26
+ permitted_time_range_columns.include?(sort_column) ? sort_column : permitted_time_range_columns.first
27
+ end
28
+
29
+ # So they can be overridden
30
+ def default_direction = "desc"
31
+
32
+ def default_column = sortable_columns.first
33
+ end
34
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Binxtils
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/binxtils.rb CHANGED
@@ -10,4 +10,7 @@ require "rails-html-sanitizer"
10
10
  require_relative "binxtils/input_normalizer"
11
11
  require_relative "binxtils/time_zone_parser"
12
12
  require_relative "binxtils/time_parser"
13
+ require_relative "binxtils/set_period"
14
+ require_relative "binxtils/sortable_table"
15
+ require_relative "binxtils/sortable_helper"
13
16
  require_relative "binxtils/railtie" if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: binxtils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bike Index
@@ -90,6 +90,9 @@ files:
90
90
  - lib/binxtils.rb
91
91
  - lib/binxtils/input_normalizer.rb
92
92
  - lib/binxtils/railtie.rb
93
+ - lib/binxtils/set_period.rb
94
+ - lib/binxtils/sortable_helper.rb
95
+ - lib/binxtils/sortable_table.rb
93
96
  - lib/binxtils/time_parser.rb
94
97
  - lib/binxtils/time_zone_parser.rb
95
98
  - lib/binxtils/version.rb