katalyst-tables 3.1.0 → 3.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +27 -0
- data/app/assets/builds/katalyst/tables.esm.js +16 -0
- data/app/assets/builds/katalyst/tables.js +16 -0
- data/app/assets/builds/katalyst/tables.min.js +1 -1
- data/app/assets/builds/katalyst/tables.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/tables/_filter.scss +43 -0
- data/app/assets/stylesheets/katalyst/tables/_index.scss +1 -0
- data/app/assets/stylesheets/katalyst/tables/_select.scss +3 -0
- data/app/assets/stylesheets/katalyst/tables/_table.scss +3 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_enum.scss +9 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_index.scss +1 -0
- data/app/components/katalyst/table_component.rb +28 -0
- data/app/components/katalyst/tables/cells/enum_component.rb +27 -0
- data/app/components/katalyst/tables/filter/modal_component.html.erb +25 -0
- data/app/components/katalyst/tables/filter/modal_component.rb +112 -0
- data/app/components/katalyst/tables/filter_component.html.erb +18 -0
- data/app/components/katalyst/tables/filter_component.rb +91 -0
- data/app/helpers/katalyst/tables/frontend.rb +8 -0
- data/app/javascript/tables/application.js +5 -0
- data/app/javascript/tables/filter/modal_controller.js +13 -0
- data/app/models/concerns/katalyst/tables/collection/core.rb +44 -0
- data/app/models/concerns/katalyst/tables/collection/filtering.rb +17 -9
- data/app/models/concerns/katalyst/tables/collection/pagination.rb +1 -1
- data/app/models/concerns/katalyst/tables/collection/query/array_value_parser.rb +56 -0
- data/app/models/concerns/katalyst/tables/collection/query/parser.rb +73 -0
- data/app/models/concerns/katalyst/tables/collection/query/single_value_parser.rb +24 -0
- data/app/models/concerns/katalyst/tables/collection/query/value_parser.rb +34 -0
- data/app/models/concerns/katalyst/tables/collection/query.rb +34 -0
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +1 -1
- data/app/models/katalyst/tables/collection/array.rb +0 -1
- data/app/models/katalyst/tables/collection/base.rb +0 -5
- data/app/models/katalyst/tables/collection/filter.rb +0 -1
- data/app/models/katalyst/tables/collection/type/boolean.rb +22 -0
- data/app/models/katalyst/tables/collection/type/date.rb +60 -0
- data/app/models/katalyst/tables/collection/type/enum.rb +21 -0
- data/app/models/katalyst/tables/collection/type/float.rb +57 -0
- data/app/models/katalyst/tables/collection/type/helpers/delegate.rb +50 -0
- data/app/models/katalyst/tables/collection/type/helpers/extensions.rb +28 -0
- data/app/models/katalyst/tables/collection/type/helpers/multiple.rb +30 -0
- data/app/models/katalyst/tables/collection/type/integer.rb +57 -0
- data/app/models/katalyst/tables/collection/type/query.rb +19 -0
- data/app/models/katalyst/tables/collection/type/search.rb +26 -0
- data/app/models/katalyst/tables/collection/type/string.rb +35 -0
- data/app/models/katalyst/tables/collection/type/value.rb +66 -0
- data/app/models/katalyst/tables/collection/type.rb +39 -0
- metadata +30 -3
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
# A component for rendering a data driven filter for a collection.
|
6
|
+
# <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) %>
|
7
|
+
#
|
8
|
+
# By default, the component will render a form containing a single text field. Interacting with the
|
9
|
+
# text field will display a dropdown outlining all available keys and values to be filtered on.
|
10
|
+
#
|
11
|
+
# You can override how the form and input displays by passing in content to the component.
|
12
|
+
# The component provides a helper function `form`
|
13
|
+
# to ensure the correct attributes and default form fields are collected.
|
14
|
+
# You can pass additional options to the `form` method to modify it.
|
15
|
+
#
|
16
|
+
# <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) do |filter| %>
|
17
|
+
# <%= filter.form(builder: GOVUKFormBuilder) do |form| %>
|
18
|
+
# <%= form.govuk_text_field :query %>
|
19
|
+
# <%= form.govuk_submit "Apply" %>
|
20
|
+
# <% end %>
|
21
|
+
# <% end %>
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# Additionally the component allows for access to the dropdown that displays when interacting with the input.
|
25
|
+
# The dropdown supports additional "footer" content to be added.
|
26
|
+
#
|
27
|
+
# <%= Katalyst::Tables::FilterComponent.new(collection: @people, url: peoples_path) do |filter| %>
|
28
|
+
# <% filter.with_modal(collection:) do |modal| %>
|
29
|
+
# <% modal.with_footer do %>
|
30
|
+
# <%= link_to "Docs", docs_path %>
|
31
|
+
# <% end %>
|
32
|
+
# <% end %>
|
33
|
+
# <% end %>
|
34
|
+
#
|
35
|
+
class FilterComponent < ViewComponent::Base
|
36
|
+
include Katalyst::HtmlAttributes
|
37
|
+
include Katalyst::Tables::Frontend
|
38
|
+
|
39
|
+
renders_one :modal, Katalyst::Tables::Filter::ModalComponent
|
40
|
+
|
41
|
+
define_html_attribute_methods :input_attributes
|
42
|
+
|
43
|
+
attr_reader :collection, :url
|
44
|
+
|
45
|
+
def initialize(collection:, url:, **)
|
46
|
+
super(**)
|
47
|
+
|
48
|
+
@collection = collection
|
49
|
+
@url = url
|
50
|
+
end
|
51
|
+
|
52
|
+
def before_render
|
53
|
+
with_modal(collection:) unless modal?
|
54
|
+
end
|
55
|
+
|
56
|
+
def form(url: @url, **options, &)
|
57
|
+
form_with(model: collection,
|
58
|
+
url:,
|
59
|
+
method: :get,
|
60
|
+
**options) do |form|
|
61
|
+
concat(form.hidden_field(:sort))
|
62
|
+
|
63
|
+
yield form if block_given?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def default_html_attributes
|
70
|
+
{
|
71
|
+
data: {
|
72
|
+
controller: "tables--filter--modal",
|
73
|
+
action: <<~ACTIONS.gsub(/\s+/, " "),
|
74
|
+
click@window->tables--filter--modal#close
|
75
|
+
click->tables--filter--modal#open:stop
|
76
|
+
keydown.esc->tables--filter--modal#close
|
77
|
+
ACTIONS
|
78
|
+
},
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_input_attributes
|
83
|
+
{
|
84
|
+
data: {
|
85
|
+
action: "focus->tables--filter--modal#open",
|
86
|
+
},
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -69,6 +69,14 @@ module Katalyst
|
|
69
69
|
render(component, &)
|
70
70
|
end
|
71
71
|
|
72
|
+
# Construct a new filter.
|
73
|
+
#
|
74
|
+
# @param collection [Katalyst::Tables::Collection::Core] the collection to render
|
75
|
+
# @param url [String] the url to submit the form to (e.g. <resources>_path)
|
76
|
+
def filter_with(collection:, url: url_for(action: :index), &)
|
77
|
+
render(FilterComponent.new(collection:, url:), &)
|
78
|
+
end
|
79
|
+
|
72
80
|
private
|
73
81
|
|
74
82
|
def default_table_component_class
|
@@ -3,6 +3,7 @@ import OrderableListController from "./orderable/list_controller";
|
|
3
3
|
import OrderableFormController from "./orderable/form_controller";
|
4
4
|
import SelectionFormController from "./selection/form_controller";
|
5
5
|
import SelectionItemController from "./selection/item_controller";
|
6
|
+
import FilterModalController from "./filter/modal_controller";
|
6
7
|
|
7
8
|
const Definitions = [
|
8
9
|
{
|
@@ -25,6 +26,10 @@ const Definitions = [
|
|
25
26
|
identifier: "tables--selection--item",
|
26
27
|
controllerConstructor: SelectionItemController,
|
27
28
|
},
|
29
|
+
{
|
30
|
+
identifier: "tables--filter--modal",
|
31
|
+
controllerConstructor: FilterModalController,
|
32
|
+
},
|
28
33
|
];
|
29
34
|
|
30
35
|
export { Definitions as default };
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class FilterModalController extends Controller {
|
4
|
+
static targets = ["modal"];
|
5
|
+
|
6
|
+
close(e) {
|
7
|
+
delete this.modalTarget.dataset.open;
|
8
|
+
}
|
9
|
+
|
10
|
+
open(e) {
|
11
|
+
this.modalTarget.dataset.open = "true";
|
12
|
+
}
|
13
|
+
}
|
@@ -25,6 +25,24 @@ module Katalyst
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
using Type::Helpers::Extensions
|
30
|
+
|
31
|
+
def attribute(name, type = nil, default: (no_default = true), **)
|
32
|
+
type = type.is_a?(Symbol) ? resolve_type_name(type, **) : type || Type::Value.new
|
33
|
+
|
34
|
+
default = type.default_value if no_default
|
35
|
+
|
36
|
+
default.nil? && no_default ? super(name, type, **) : super
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @override ActiveModel::AttributeRegistration::ClassMethods#resolve_type_name()
|
42
|
+
def resolve_type_name(name, **)
|
43
|
+
# note, this is Katalyst::Tables::Collection::Type, not ActiveModel::Type
|
44
|
+
Type.lookup(name, **)
|
45
|
+
end
|
28
46
|
end
|
29
47
|
|
30
48
|
included do
|
@@ -39,11 +57,21 @@ module Katalyst
|
|
39
57
|
clear_changes_information
|
40
58
|
end
|
41
59
|
|
60
|
+
# Collections are filtered when any parameters have changed from their defaults.
|
61
|
+
def filtered?
|
62
|
+
filters.any?
|
63
|
+
end
|
64
|
+
|
42
65
|
# Collections that do not include Sorting are never sortable.
|
43
66
|
def sortable?
|
44
67
|
false
|
45
68
|
end
|
46
69
|
|
70
|
+
# Collections that do not include Query are never searchable.
|
71
|
+
def searchable?
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
47
75
|
def apply(items)
|
48
76
|
@items = items
|
49
77
|
reducers.build do |_|
|
@@ -52,6 +80,22 @@ module Katalyst
|
|
52
80
|
end.call(self)
|
53
81
|
self
|
54
82
|
end
|
83
|
+
|
84
|
+
def filter
|
85
|
+
# no-op by default
|
86
|
+
end
|
87
|
+
|
88
|
+
def filters
|
89
|
+
changes.except("sort", "page", "query").transform_values(&:second)
|
90
|
+
end
|
91
|
+
|
92
|
+
def model
|
93
|
+
if items < ActiveRecord::Base
|
94
|
+
items
|
95
|
+
else
|
96
|
+
items.model
|
97
|
+
end
|
98
|
+
end
|
55
99
|
end
|
56
100
|
end
|
57
101
|
end
|
@@ -3,19 +3,27 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
5
|
module Collection
|
6
|
-
|
6
|
+
module Filtering
|
7
|
+
extend ActiveSupport::Concern
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
# no-op by default
|
9
|
+
included do
|
10
|
+
use(Filter)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
class Filter
|
14
|
+
include ActiveRecord::Sanitization::ClassMethods
|
15
|
+
|
16
|
+
def initialize(app)
|
17
|
+
@app = app
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(collection)
|
21
|
+
collection.instance_variable_get(:@attributes).each_value do |attribute|
|
22
|
+
collection.items = attribute.type.filter(collection.items, attribute)
|
23
|
+
end
|
16
24
|
|
17
|
-
|
18
|
-
|
25
|
+
@app.call(collection)
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Query
|
7
|
+
class ArrayValueParser < ValueParser
|
8
|
+
# @param query [StringScanner]
|
9
|
+
def parse(query)
|
10
|
+
@query = query
|
11
|
+
|
12
|
+
skip_whitespace
|
13
|
+
|
14
|
+
if query.scan(/#{'\['}/)
|
15
|
+
take_values
|
16
|
+
else
|
17
|
+
take_value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def take_values
|
22
|
+
until query.eos?
|
23
|
+
skip_whitespace
|
24
|
+
break unless take_quoted_value || take_unquoted_value
|
25
|
+
|
26
|
+
skip_whitespace
|
27
|
+
break unless take_delimiter
|
28
|
+
end
|
29
|
+
|
30
|
+
skip_whitespace
|
31
|
+
take_end_of_list
|
32
|
+
end
|
33
|
+
|
34
|
+
def take_value
|
35
|
+
take_quoted_value || take_unquoted_value
|
36
|
+
end
|
37
|
+
|
38
|
+
def take_delimiter
|
39
|
+
query.scan(/#{','}/)
|
40
|
+
end
|
41
|
+
|
42
|
+
def take_end_of_list
|
43
|
+
query.scan(/#{']'}/)
|
44
|
+
end
|
45
|
+
|
46
|
+
def value=(value)
|
47
|
+
return if @attribute.type_cast(value).nil? # undefined attribute
|
48
|
+
|
49
|
+
current = @collection.attributes[@attribute.name]
|
50
|
+
@collection.assign_attributes(@attribute.name => current + [value])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Query
|
7
|
+
class Parser # :nodoc:
|
8
|
+
# query [StringScanner]
|
9
|
+
attr_accessor :query
|
10
|
+
attr_reader :collection, :untagged
|
11
|
+
|
12
|
+
def initialize(collection)
|
13
|
+
@collection = collection
|
14
|
+
@untagged = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param query [String]
|
18
|
+
def parse(query)
|
19
|
+
@query = StringScanner.new(query)
|
20
|
+
|
21
|
+
until @query.eos?
|
22
|
+
skip_whitespace
|
23
|
+
|
24
|
+
# break to ensure we don't loop indefinitely on bad input
|
25
|
+
break unless take_tagged || take_untagged
|
26
|
+
end
|
27
|
+
|
28
|
+
if untagged.any? && (search = collection.class.search_attribute)
|
29
|
+
collection.assign_attributes(search => untagged.join(" "))
|
30
|
+
end
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def skip_whitespace
|
38
|
+
query.scan(/\s+/)
|
39
|
+
end
|
40
|
+
|
41
|
+
def take_tagged
|
42
|
+
return unless query.scan(/(\w+(\.\w+)?):/)
|
43
|
+
|
44
|
+
key, = query.values_at(1)
|
45
|
+
skip_whitespace
|
46
|
+
|
47
|
+
parser_for(key).parse(query)
|
48
|
+
end
|
49
|
+
|
50
|
+
def take_untagged
|
51
|
+
return unless query.scan(/\S+/)
|
52
|
+
|
53
|
+
untagged << query.matched
|
54
|
+
|
55
|
+
untagged
|
56
|
+
end
|
57
|
+
|
58
|
+
using Type::Helpers::Extensions
|
59
|
+
|
60
|
+
def parser_for(key)
|
61
|
+
attribute = collection.class._default_attributes[key]
|
62
|
+
|
63
|
+
if attribute.type.multiple? || attribute.value.is_a?(::Array)
|
64
|
+
ArrayValueParser.new(collection:, attribute:)
|
65
|
+
else
|
66
|
+
SingleValueParser.new(collection:, attribute:)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Query
|
7
|
+
class SingleValueParser < ValueParser
|
8
|
+
# @param query [StringScanner]
|
9
|
+
def parse(query)
|
10
|
+
@query = query
|
11
|
+
|
12
|
+
take_quoted_value || take_unquoted_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def value=(value)
|
16
|
+
return if @attribute.type_cast(value).nil? # undefined attribute
|
17
|
+
|
18
|
+
@collection.assign_attributes(@attribute.name => value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Query
|
7
|
+
class ValueParser
|
8
|
+
attr_accessor :query
|
9
|
+
|
10
|
+
def initialize(collection:, attribute:)
|
11
|
+
@collection = collection
|
12
|
+
@attribute = attribute
|
13
|
+
end
|
14
|
+
|
15
|
+
def take_quoted_value
|
16
|
+
return unless query.scan(/"([^"]*)"/)
|
17
|
+
|
18
|
+
self.value, = query.values_at(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
def take_unquoted_value
|
22
|
+
return unless query.scan(/([^" \],]*)/)
|
23
|
+
|
24
|
+
self.value, = query.values_at(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def skip_whitespace
|
28
|
+
query.scan(/\s+/)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Query # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include Filtering
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def search_attribute
|
13
|
+
_default_attributes.each_value do |attribute|
|
14
|
+
return attribute.name if attribute.type.type == :search
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
included do
|
20
|
+
attribute :query, :query, default: ""
|
21
|
+
|
22
|
+
# Note: this is defined inline so that we can overwrite query=
|
23
|
+
def query=(value)
|
24
|
+
query = super
|
25
|
+
|
26
|
+
Parser.new(self).parse(query)
|
27
|
+
|
28
|
+
query
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -23,7 +23,6 @@ module Katalyst
|
|
23
23
|
# ````
|
24
24
|
class Base
|
25
25
|
include Core
|
26
|
-
include Filtering
|
27
26
|
include Pagination
|
28
27
|
include Sorting
|
29
28
|
|
@@ -34,10 +33,6 @@ module Katalyst
|
|
34
33
|
new.with_params(params)
|
35
34
|
end
|
36
35
|
|
37
|
-
def model
|
38
|
-
items.model
|
39
|
-
end
|
40
|
-
|
41
36
|
def model_name
|
42
37
|
@model_name ||= items.model_name.dup.tap do |name|
|
43
38
|
name.param_key = ""
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Type
|
7
|
+
class Boolean < Value
|
8
|
+
include Helpers::Delegate
|
9
|
+
include Helpers::Multiple
|
10
|
+
|
11
|
+
def initialize(**)
|
12
|
+
super(**, delegate: ActiveModel::Type::Boolean)
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter?(attribute, value)
|
16
|
+
(!value.nil? && !value.eql?([])) || attribute.came_from_user?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Type
|
7
|
+
class Date < Value
|
8
|
+
def type
|
9
|
+
:date
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
if value.is_a?(Date)
|
14
|
+
value.to_fs(:db)
|
15
|
+
elsif value.is_a?(Range)
|
16
|
+
if value.begin.nil?
|
17
|
+
"<#{value.end.to_fs(:db)}"
|
18
|
+
elsif value.end.nil?
|
19
|
+
">#{value.begin.to_fs(:db)}"
|
20
|
+
else
|
21
|
+
"#{value.begin.to_fs(:db)}..#{value.end.to_fs(:db)}"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
value.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
31
|
+
LOWER_BOUND = /\A>(\d{4})-(\d\d)-(\d\d)\z/
|
32
|
+
UPPER_BOUND = /\A<(\d{4})-(\d\d)-(\d\d)\z/
|
33
|
+
BOUNDED = /\A(\d{4})-(\d\d)-(\d\d)\.\.(\d{4})-(\d\d)-(\d\d)\z/
|
34
|
+
|
35
|
+
def cast_value(value)
|
36
|
+
return value unless value.is_a?(::String)
|
37
|
+
|
38
|
+
if value =~ ISO_DATE
|
39
|
+
new_date($1.to_i, $2.to_i, $3.to_i)
|
40
|
+
elsif value =~ LOWER_BOUND
|
41
|
+
(new_date($1.to_i, $2.to_i, $3.to_i)..)
|
42
|
+
elsif value =~ UPPER_BOUND
|
43
|
+
(..new_date($1.to_i, $2.to_i, $3.to_i))
|
44
|
+
elsif value =~ BOUNDED
|
45
|
+
(new_date($1.to_i, $2.to_i, $3.to_i)..new_date($4.to_i, $5.to_i, $6.to_i))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def new_date(year, mon, mday)
|
50
|
+
return nil if year.nil? || (year.zero? && mon.zero? && mday.zero?)
|
51
|
+
|
52
|
+
::Date.new(year, mon, mday)
|
53
|
+
rescue ArgumentError, TypeError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Type
|
7
|
+
class Enum < Value
|
8
|
+
include Helpers::Multiple
|
9
|
+
|
10
|
+
def initialize(multiple: true, **)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
:enum
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Type
|
7
|
+
class Float < Value
|
8
|
+
include Helpers::Delegate
|
9
|
+
include Helpers::Multiple
|
10
|
+
|
11
|
+
def initialize(**)
|
12
|
+
super(**, delegate: ActiveModel::Type::Float)
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize(value)
|
16
|
+
if value.is_a?(Range)
|
17
|
+
if value.begin.nil?
|
18
|
+
"<#{super(value.end)}"
|
19
|
+
elsif value.end.nil?
|
20
|
+
">#{super(value.begin)}"
|
21
|
+
else
|
22
|
+
"#{super(value.begin)}..#{super(value.end)}"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
FLOAT = /(-?\d+(?:\.\d+)?)/
|
32
|
+
SINGLE_VALUE = /\A#{FLOAT}\z/
|
33
|
+
LOWER_BOUND = /\A>#{FLOAT}\z/
|
34
|
+
UPPER_BOUND = /\A<#{FLOAT}\z/
|
35
|
+
BOUNDED = /\A#{FLOAT}\.\.#{FLOAT}\z/
|
36
|
+
|
37
|
+
def cast_value(value)
|
38
|
+
case value
|
39
|
+
when ::Range, ::Integer
|
40
|
+
value
|
41
|
+
when SINGLE_VALUE
|
42
|
+
super($1)
|
43
|
+
when LOWER_BOUND
|
44
|
+
((super($1))..)
|
45
|
+
when UPPER_BOUND
|
46
|
+
(..(super($1)))
|
47
|
+
when BOUNDED
|
48
|
+
((super($1))..(super($2)))
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|