katalyst-tables 3.1.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|