cafe_car 0.1.1 → 0.1.2
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/README.md +155 -40
- data/Rakefile +9 -1
- data/app/assets/fonts/Lexend.css +7 -0
- data/app/assets/fonts/Lexend.ttf +0 -0
- data/app/assets/stylesheets/cafe_car/themes/defaults.css +58 -59
- data/app/assets/stylesheets/cafe_car/tooltips.css +20 -0
- data/app/assets/stylesheets/cafe_car/utility.css +1 -2
- data/app/assets/stylesheets/cafe_car.css +17 -6
- data/app/assets/stylesheets/ui/Alert.css +2 -1
- data/app/assets/stylesheets/ui/Button.css +6 -6
- data/app/assets/stylesheets/ui/Card.css +7 -3
- data/app/assets/stylesheets/ui/Chat.css +33 -0
- data/app/assets/stylesheets/ui/Close.css +11 -0
- data/app/assets/stylesheets/ui/Grid.css +2 -0
- data/app/assets/stylesheets/ui/Icon.css +3 -3
- data/app/assets/stylesheets/ui/Layout.css +20 -13
- data/app/assets/stylesheets/ui/Modal.css +5 -12
- data/app/assets/stylesheets/ui/Navigation.css +13 -5
- data/app/assets/stylesheets/ui/Page.css +42 -3
- data/app/assets/stylesheets/ui/Table.css +27 -56
- data/app/assets/stylesheets/ui/components.css +2 -0
- data/app/controllers/cafe_car/examples_controller.rb +1 -1
- data/app/controllers/cafe_car/sessions_controller.rb +30 -0
- data/app/controllers/concerns/cafe_car/authentication.rb +61 -0
- data/app/javascript/cafe_car.js +16 -11
- data/app/models/cafe_car/session.rb +18 -0
- data/app/policies/cafe_car/session_policy.rb +19 -0
- data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +5 -4
- data/app/presenters/cafe_car/code_presenter.rb +18 -0
- data/app/presenters/cafe_car/date_presenter.rb +1 -0
- data/app/presenters/cafe_car/date_time_presenter.rb +2 -2
- data/app/presenters/cafe_car/enumerable_presenter.rb +1 -1
- data/app/presenters/cafe_car/hash_presenter.rb +3 -8
- data/app/presenters/cafe_car/presenter.rb +22 -12
- data/app/presenters/cafe_car/string_presenter.rb +2 -2
- data/app/ui/cafe_car/ui/button.rb +2 -1
- data/app/ui/cafe_car/ui/card.rb +18 -0
- data/app/ui/cafe_car/ui/grid.rb +30 -0
- data/app/ui/cafe_car/ui/layout.rb +7 -0
- data/app/ui/cafe_car/ui/page.rb +5 -1
- data/app/views/application/_body.html.haml +2 -1
- data/app/views/application/_controls.html.haml +1 -0
- data/app/views/application/_debug.html.haml +9 -2
- data/app/views/application/_errors.html.haml +4 -8
- data/app/views/application/_grid.html.haml +1 -1
- data/app/views/application/_grid_item.html.haml +1 -1
- data/app/views/application/_head.html.haml +1 -0
- data/app/views/application/_index.html.haml +6 -2
- data/app/views/application/_index_actions.html.haml +3 -3
- data/app/views/application/_navigation.html.haml +7 -0
- data/app/views/application/_navigation_links.html.haml +1 -1
- data/app/views/application/_notes.html.haml +1 -0
- data/app/views/application/_popup.html.haml +7 -0
- data/app/views/cafe_car/application/edit.html.haml +1 -1
- data/app/views/cafe_car/application/edit.turbo_stream.haml +3 -5
- data/app/views/cafe_car/application/index.html.haml +3 -0
- data/app/views/cafe_car/application/new.turbo_stream.haml +5 -6
- data/app/views/cafe_car/application/show.html.haml +2 -2
- data/app/views/cafe_car/examples/ui/_chat.html.haml +3 -0
- data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -1
- data/app/views/cafe_car/examples/ui/_modal.html.haml +1 -1
- data/app/views/passwords_mailer/reset.html.haml +5 -0
- data/app/views/passwords_mailer/reset.text.erb +4 -0
- data/app/views/ui/_card.html.haml +6 -11
- data/app/views/ui/_field.html.haml +1 -7
- data/app/views/ui/_modal_close.html.haml +1 -2
- data/app/views/ui/_page.html.haml +6 -12
- data/config/brakeman.ignore +3 -3
- data/config/locales/en.yml +10 -2
- data/config/routes.rb +5 -1
- data/db/migrate/20251005220017_create_slugs.rb +2 -2
- data/lib/cafe_car/active_record.rb +1 -1
- data/lib/cafe_car/application_responder.rb +6 -0
- data/lib/cafe_car/attributes.rb +1 -1
- data/lib/cafe_car/auto_resolver.rb +1 -1
- data/lib/cafe_car/component.rb +102 -39
- data/lib/cafe_car/context.rb +5 -4
- data/lib/cafe_car/controller/filtering.rb +9 -1
- data/lib/cafe_car/controller.rb +52 -13
- data/lib/cafe_car/core_ext/array.rb +13 -0
- data/lib/cafe_car/core_ext/hash.rb +15 -0
- data/lib/cafe_car/core_ext/module.rb +15 -0
- data/lib/cafe_car/core_ext.rb +0 -2
- data/lib/cafe_car/current.rb +4 -1
- data/lib/cafe_car/engine.rb +9 -2
- data/lib/cafe_car/field_builder.rb +1 -1
- data/lib/cafe_car/field_info.rb +14 -12
- data/lib/cafe_car/fields.rb +7 -0
- data/lib/cafe_car/filter/field_info.rb +1 -1
- data/lib/cafe_car/filter/form_builder.rb +2 -2
- data/lib/cafe_car/filter_builder.rb +1 -1
- data/lib/cafe_car/form_builder.rb +1 -1
- data/lib/cafe_car/generators.rb +1 -1
- data/lib/cafe_car/helpers.rb +37 -10
- data/lib/cafe_car/href_builder.rb +35 -9
- data/lib/cafe_car/input_builder.rb +1 -1
- data/lib/cafe_car/link_builder.rb +14 -11
- data/lib/cafe_car/model.rb +2 -2
- data/lib/cafe_car/navigation.rb +10 -10
- data/lib/cafe_car/option_helpers.rb +11 -5
- data/lib/cafe_car/param_parser.rb +10 -6
- data/lib/cafe_car/policy.rb +2 -2
- data/lib/cafe_car/query_builder.rb +3 -3
- data/lib/cafe_car/resolver.rb +5 -1
- data/lib/cafe_car/routing.rb +1 -1
- data/lib/cafe_car/table/builder.rb +3 -2
- data/lib/cafe_car/table/head_builder.rb +2 -2
- data/lib/cafe_car/table/label_builder.rb +1 -1
- data/lib/cafe_car/table/row_builder.rb +5 -7
- data/lib/cafe_car/table_builder.rb +3 -3
- data/lib/cafe_car/ui.rb +2 -0
- data/lib/cafe_car/version.rb +1 -1
- data/lib/cafe_car/visitors.rb +2 -2
- data/lib/cafe_car.rb +25 -0
- data/lib/generators/cafe_car/controller/templates/controller.rb.tt +1 -1
- data/lib/generators/cafe_car/install/install_generator.rb +0 -1
- data/lib/generators/cafe_car/sessions/USAGE +17 -0
- data/lib/generators/cafe_car/sessions/sessions_generator.rb +29 -0
- data/lib/generators/cafe_car/sessions/templates/create_sessions.rb.tt +12 -0
- data/lib/tasks/holdco_tasks.rake +532 -0
- data/lib/tasks/templates/tasks_header.md +37 -0
- metadata +76 -48
- data/app/models/cafe_car/slug.rb +0 -3
- data/app/views/ui/_grid.html.haml +0 -17
- data/app/views/ui/_layout_menu.html.haml +0 -2
data/config/routes.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
CafeCar::Engine.routes.draw do
|
|
2
2
|
scope module: :cafe_car, as: :cafe_car do
|
|
3
|
-
get
|
|
3
|
+
get "components", to: "examples#index"
|
|
4
4
|
end
|
|
5
|
+
|
|
6
|
+
# Opt-in login/logout. Singular resource so the form posts to /session and
|
|
7
|
+
# request_authentication can redirect to new_session_path.
|
|
8
|
+
resource :session, only: %i[new create destroy], controller: "cafe_car/sessions"
|
|
5
9
|
end
|
|
@@ -7,7 +7,7 @@ class CreateSlugs < ActiveRecord::Migration[8.0]
|
|
|
7
7
|
t.datetime :created_at
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
add_index :slugs, [:slug, :sluggable_type]
|
|
11
|
-
add_index :slugs, [:slug, :sluggable_type, :scope], unique: true
|
|
10
|
+
add_index :slugs, [ :slug, :sluggable_type ]
|
|
11
|
+
add_index :slugs, [ :slug, :sluggable_type, :scope ], unique: true
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -9,7 +9,7 @@ module CafeCar::ActiveRecord
|
|
|
9
9
|
def initialize(*args)
|
|
10
10
|
pre_cafe_car_initialize(*args)
|
|
11
11
|
|
|
12
|
-
raw_connection.create_function(
|
|
12
|
+
raw_connection.create_function("regexp", -1) do |func, pattern, expression, case_sensitive|
|
|
13
13
|
options = 0
|
|
14
14
|
options |= Regexp::IGNORECASE if case_sensitive.zero?
|
|
15
15
|
pattern = Regexp.new(pattern.to_s, options)
|
|
@@ -7,5 +7,11 @@ module CafeCar
|
|
|
7
7
|
|
|
8
8
|
self.error_status = :unprocessable_entity
|
|
9
9
|
self.redirect_status = :see_other
|
|
10
|
+
|
|
11
|
+
def to_turbo_stream
|
|
12
|
+
# Put :html back in the accepted format list. respond_with removes it
|
|
13
|
+
controller.lookup_context.formats << :html
|
|
14
|
+
to_html
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
17
|
end
|
data/lib/cafe_car/attributes.rb
CHANGED
data/lib/cafe_car/component.rb
CHANGED
|
@@ -1,68 +1,133 @@
|
|
|
1
1
|
module CafeCar
|
|
2
2
|
class Component
|
|
3
|
-
include OptionHelpers
|
|
4
|
-
|
|
5
3
|
concerning :Macros do
|
|
4
|
+
included do
|
|
5
|
+
class_attribute :option_defaults, default: {}
|
|
6
|
+
class_attribute :attribute_defaults, default: {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def extract_options!(options)
|
|
10
|
+
@options = options.extract!(*option_defaults.keys).with_defaults!(option_defaults)
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
class_methods do
|
|
14
|
+
def inherited(subclass)
|
|
15
|
+
super
|
|
16
|
+
subclass.option_defaults = option_defaults.deep_dup
|
|
17
|
+
subclass.attribute_defaults = attribute_defaults.deep_dup
|
|
18
|
+
end
|
|
19
|
+
|
|
7
20
|
def component(*names, **, &)
|
|
8
21
|
names.each do |name|
|
|
9
22
|
define_class(name, CafeCar[:Component], **, &)
|
|
10
23
|
end
|
|
11
24
|
end
|
|
25
|
+
|
|
26
|
+
def flag(flag)
|
|
27
|
+
include Module.new do
|
|
28
|
+
define_method(flag) { |v| @options[flag] = v.nil? ? true : v }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def option(name, default: nil, accessor: true, reader: accessor, writer: accessor, presence: accessor, macro: accessor)
|
|
33
|
+
option_defaults[name] = default
|
|
34
|
+
define_singleton_method(name) { |v| option_defaults[name] = v } if macro
|
|
35
|
+
include Module.new {
|
|
36
|
+
define_method(name) { options[name] } if reader
|
|
37
|
+
define_method("#{name}=") { |v| options[name] = v } if writer
|
|
38
|
+
define_method("#{name}?") { options[name].present? } if presence
|
|
39
|
+
}
|
|
40
|
+
end
|
|
12
41
|
end
|
|
13
42
|
end
|
|
14
43
|
|
|
15
|
-
|
|
44
|
+
def self.method_missing(name, v)
|
|
45
|
+
attribute_defaults[name] = v
|
|
46
|
+
end
|
|
16
47
|
|
|
17
|
-
|
|
48
|
+
attr_reader :flags, :options, :attributes
|
|
18
49
|
|
|
19
|
-
|
|
50
|
+
delegate :render, :capture, :safe_join, :ui_class, :context?, to: :@template
|
|
20
51
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
option :tag, default: :div
|
|
53
|
+
option :class, accessor: false
|
|
54
|
+
option :data, default: {}
|
|
55
|
+
option :href
|
|
56
|
+
option :tip
|
|
57
|
+
|
|
58
|
+
def initialize(template, name, *args, **attributes, &block)
|
|
59
|
+
@template = template
|
|
60
|
+
@names = [ *name ].map(&:underscore)
|
|
61
|
+
@flags = args.extract! { _1.is_a? Symbol }
|
|
62
|
+
@args = args.flatten.compact_blank
|
|
63
|
+
@attributes = attributes.with_defaults!(attribute_defaults)
|
|
64
|
+
@children = attributes.extract_if! { _1 =~ /^[A-Z]\w*$/ }
|
|
65
|
+
@block = block
|
|
66
|
+
extract_options!(@attributes)
|
|
31
67
|
end
|
|
32
68
|
|
|
33
69
|
def name = @names.last
|
|
34
|
-
def
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
def
|
|
70
|
+
def tag = href? ? :a : super
|
|
71
|
+
def href? = super && !context?(:a) && !current_href?
|
|
72
|
+
|
|
73
|
+
def href
|
|
74
|
+
@template.href_for(super) if href?
|
|
75
|
+
end
|
|
38
76
|
|
|
39
77
|
def current_href? = options[:href]&.then { @template.current_href?(_1, check_parameters: true) }
|
|
40
78
|
def ancestor_href? = options[:href]&.then { @template.ancestor_href?(_1) }
|
|
41
79
|
|
|
42
|
-
def
|
|
80
|
+
def data
|
|
81
|
+
super.merge({ tip: }.compact_blank)
|
|
82
|
+
end
|
|
43
83
|
|
|
44
|
-
def
|
|
45
|
-
def
|
|
84
|
+
def partial? = @template.partial?(partial_name)
|
|
85
|
+
def partial_name = "ui/" + @names.join("_")
|
|
86
|
+
|
|
87
|
+
def render_partial
|
|
88
|
+
render(partial_name, options:, flags:, name => self, **options) { content }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def selector = class_names.join(?_).then { ?. + _1 }
|
|
92
|
+
|
|
93
|
+
def class_names = @names.map(&:camelize)
|
|
94
|
+
|
|
95
|
+
def class_name
|
|
96
|
+
@template.ui_class(class_names, *flags, *options[:class], options[:tag].to_s => href?,
|
|
97
|
+
current: current_href?,
|
|
98
|
+
ancestor: ancestor_href? && !current_href?)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def attributes
|
|
102
|
+
@attributes.merge(class: class_name, href:, data:)
|
|
103
|
+
end
|
|
46
104
|
|
|
47
105
|
def children
|
|
48
|
-
@children.map {|name, content| send(name) { content } }
|
|
106
|
+
@children.map { |name, content| send(name) { content } }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def blank?
|
|
110
|
+
@block and body and content.blank? || !content.match?(/^.*?[^<\s]/) || content.gsub(/<!--.*?-->/, "").blank?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def body
|
|
114
|
+
@body ||= context { partial? ? render_partial : content }
|
|
49
115
|
end
|
|
50
116
|
|
|
51
117
|
def content
|
|
52
|
-
@content ||=
|
|
118
|
+
@content ||= safe_join [ *children, *@args, *(capture(self, &@block) if @block) ]
|
|
53
119
|
end
|
|
54
120
|
|
|
55
|
-
def
|
|
56
|
-
@template.
|
|
57
|
-
capture(self, &)
|
|
58
|
-
end
|
|
121
|
+
def context(&)
|
|
122
|
+
href? ? @template.context(:a, &) : capture(&)
|
|
59
123
|
end
|
|
60
124
|
|
|
61
|
-
def
|
|
62
|
-
|
|
125
|
+
def wrapper(&)
|
|
126
|
+
@template.content_tag(tag, **attributes, &)
|
|
63
127
|
end
|
|
64
128
|
|
|
65
|
-
def
|
|
129
|
+
def ~@ = @template.concat(self)
|
|
130
|
+
def +(o) = safe_join([ self, o ])
|
|
66
131
|
def <<(o) = @template.concat(o)
|
|
67
132
|
|
|
68
133
|
def html_safe? = true
|
|
@@ -70,16 +135,14 @@ module CafeCar
|
|
|
70
135
|
def to_s = to_html
|
|
71
136
|
|
|
72
137
|
def to_html
|
|
73
|
-
return "" if
|
|
74
|
-
|
|
75
|
-
if partial?
|
|
76
|
-
render(partial_name, options:, flags:, c: self, component: self, name => self, **options) { content }
|
|
77
|
-
else
|
|
78
|
-
wrapper(*@args, **@options) { content }
|
|
79
|
-
end
|
|
138
|
+
return "" if blank?
|
|
139
|
+
wrapper { body }
|
|
80
140
|
end
|
|
81
141
|
|
|
82
|
-
def child(name, ...)
|
|
142
|
+
def child(name, ...)
|
|
143
|
+
c = self.class.try { const_defined?(name) ? const_get(name) : Component }
|
|
144
|
+
c.new(@template, [ *@names, name ], ...)
|
|
145
|
+
end
|
|
83
146
|
|
|
84
147
|
def method_missing(name, ...)
|
|
85
148
|
if name =~ /^[A-Z]/
|
data/lib/cafe_car/context.rb
CHANGED
|
@@ -5,12 +5,13 @@ module CafeCar
|
|
|
5
5
|
@prefix = prefix
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def class(name, ...) = @template.ui_class([*@prefix, *name], ...)
|
|
9
|
-
def wrapper(...) = Component.new(@template, [*@prefix], ...).wrapper(...)
|
|
8
|
+
def class(name, ...) = @template.ui_class([ *@prefix, *name ], ...)
|
|
9
|
+
# def wrapper(...) = Component.new(@template, [*@prefix], ...).wrapper(...)
|
|
10
10
|
def <<(obj) = @template.concat(obj)
|
|
11
11
|
|
|
12
|
-
def method_missing(
|
|
13
|
-
|
|
12
|
+
def method_missing(name, ...)
|
|
13
|
+
c = CafeCar[:UI].const_try(name) || Component
|
|
14
|
+
c.new(@template, [ *@prefix, name ], ...)
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -2,7 +2,7 @@ module CafeCar::Controller::Filtering
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
included do
|
|
5
|
-
helper_method :parsed_params
|
|
5
|
+
helper_method :parsed_params, :dot_params, :filtered?
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
private
|
|
@@ -11,6 +11,14 @@ module CafeCar::Controller::Filtering
|
|
|
11
11
|
scope.query(parsed_params[""])
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def filtered?
|
|
15
|
+
parsed_params[""].present?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def dot_params
|
|
19
|
+
request.params.slice(*request.params.keys.grep(/^\./))
|
|
20
|
+
end
|
|
21
|
+
|
|
14
22
|
def parsed_params
|
|
15
23
|
@parsed_params ||=
|
|
16
24
|
if request.get? || request.head?
|
data/lib/cafe_car/controller.rb
CHANGED
|
@@ -5,21 +5,30 @@ module CafeCar
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
include Pundit::Authorization
|
|
8
|
-
include Filtering
|
|
8
|
+
include Filtering, Authentication
|
|
9
9
|
|
|
10
10
|
class_methods do
|
|
11
11
|
def model(model)
|
|
12
12
|
define_method(:model) { @model ||= model }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def
|
|
15
|
+
def default_view(v = @default_view || "table")
|
|
16
|
+
@default_view = v.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cafe_car(only: nil, except: nil, model: nil)
|
|
16
20
|
_only = ->(actions) do
|
|
17
21
|
actions -= except if except
|
|
18
22
|
actions &= only if only
|
|
19
23
|
actions
|
|
20
24
|
end
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
self.model model if model
|
|
27
|
+
|
|
28
|
+
rescue_from ::ActiveRecord::RecordInvalid,
|
|
29
|
+
::ActiveModel::ValidationError, with: :render_invalid_object
|
|
30
|
+
|
|
31
|
+
rescue_from ::Pundit::NotAuthorizedError, with: :render_unauthorized
|
|
23
32
|
|
|
24
33
|
append_cafe_car_views
|
|
25
34
|
|
|
@@ -35,8 +44,24 @@ module CafeCar
|
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
def append_cafe_car_views
|
|
38
|
-
append_view_path CafeCar::Engine.root.join(
|
|
39
|
-
append_view_path
|
|
47
|
+
append_view_path CafeCar::Engine.root.join("app/views/cafe_car")
|
|
48
|
+
append_view_path "app/views/cafe_car"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def define_callbacks_with_helpers(*names, **)
|
|
52
|
+
define_callbacks(*names, **)
|
|
53
|
+
|
|
54
|
+
names.each do |name|
|
|
55
|
+
%i[before around after].each do |kind|
|
|
56
|
+
define_singleton_method "#{kind}_#{name}" do |*args, **opts|
|
|
57
|
+
set_callback(name, kind, *args, prepend: true, **opts)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
define_singleton_method "skip_#{kind}_#{name}" do |*args|
|
|
61
|
+
skip_callback(name, kind, *args)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
40
65
|
end
|
|
41
66
|
end
|
|
42
67
|
|
|
@@ -44,11 +69,11 @@ module CafeCar
|
|
|
44
69
|
self.responder = CafeCar[:ApplicationResponder]
|
|
45
70
|
default_form_builder CafeCar[:FormBuilder]
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
define_callbacks_with_helpers :render, :update, :create, :destroy
|
|
48
73
|
add_flash_types :success, :warning, :error
|
|
49
74
|
|
|
50
75
|
helper_method :model, :model_name, :object, :objects
|
|
51
|
-
helper_method :action, :scope, :view
|
|
76
|
+
helper_method :action, :scope, :view, :default_view
|
|
52
77
|
|
|
53
78
|
helper Helpers
|
|
54
79
|
delegate :present, :href_for, :namespace, to: :helpers
|
|
@@ -92,8 +117,6 @@ module CafeCar
|
|
|
92
117
|
flash[:success] = present(object).i18n("#{action_name}_html", scope: :flashes)
|
|
93
118
|
end
|
|
94
119
|
|
|
95
|
-
def current_user = CafeCar[:Current].user
|
|
96
|
-
|
|
97
120
|
def sorted(scope)
|
|
98
121
|
scope.sorted(*params[:sort].presence)
|
|
99
122
|
end
|
|
@@ -139,12 +162,24 @@ module CafeCar
|
|
|
139
162
|
def model_name = model.model_name
|
|
140
163
|
|
|
141
164
|
def model
|
|
142
|
-
@model ||= self.class.name.gsub(/.*::|Controller$/,
|
|
165
|
+
@model ||= self.class.name.gsub(/.*::|Controller$/, "")
|
|
143
166
|
.classify
|
|
144
167
|
.then { self.class.module_parent.const_get _1 }
|
|
145
168
|
end
|
|
146
169
|
|
|
147
|
-
def
|
|
170
|
+
def render_invalid_object
|
|
171
|
+
render(object.persisted? ? "edit" : "new", status: :unprocessable_content)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def render_unauthorized
|
|
175
|
+
if !sessions_available?
|
|
176
|
+
head :forbidden
|
|
177
|
+
elsif authenticated?
|
|
178
|
+
redirect_back_or_to root_path
|
|
179
|
+
else
|
|
180
|
+
request_authentication
|
|
181
|
+
end
|
|
182
|
+
end
|
|
148
183
|
|
|
149
184
|
# def default_render(...) = run_callbacks(:render) { super }
|
|
150
185
|
|
|
@@ -156,12 +191,16 @@ module CafeCar
|
|
|
156
191
|
@action ||= ActiveSupport::StringInquirer.new(action_name)
|
|
157
192
|
end
|
|
158
193
|
|
|
194
|
+
def default_view = self.class.default_view
|
|
159
195
|
def view
|
|
160
|
-
params.fetch(:view) {
|
|
196
|
+
params.fetch(:view) { default_view }
|
|
161
197
|
end
|
|
162
198
|
|
|
163
199
|
def _render_with_renderer_json(obj, options)
|
|
164
|
-
|
|
200
|
+
# permitted_attributes is record-oriented, so ask a record for the column
|
|
201
|
+
# list even when serializing a collection.
|
|
202
|
+
record = obj.is_a?(CafeCar::Model) ? obj : obj.klass.new
|
|
203
|
+
options[:only] ||= [ :id ] | policy(record).displayable_attributes
|
|
165
204
|
|
|
166
205
|
if obj.is_a?(CafeCar::Model)
|
|
167
206
|
options[:include] ||= policy(obj).displayable_associations
|
|
@@ -8,4 +8,17 @@ class Array
|
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
def extract!(pattern = nil, &block)
|
|
13
|
+
block = -> { pattern === _1 } if pattern
|
|
14
|
+
return to_enum(:extract!) { size } unless block
|
|
15
|
+
|
|
16
|
+
extracted_elements = []
|
|
17
|
+
|
|
18
|
+
reject! do |element|
|
|
19
|
+
extracted_elements << element if block.(element)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
extracted_elements
|
|
23
|
+
end
|
|
11
24
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Hash
|
|
4
|
+
# Removes and returns a hash containing the key/value pairs for which the
|
|
5
|
+
# block returns a true value given the key.
|
|
6
|
+
#
|
|
7
|
+
# hash = {:a => 1, "b" => 2, :c => 3}
|
|
8
|
+
# hash.extract_if! { _1.is_a? Symbol } #=> {a: 1, c: 3}
|
|
9
|
+
# hash #=> {"b" => 2 }
|
|
10
|
+
def extract_if!
|
|
11
|
+
each_with_object(self.class.new) do |(key, value), result|
|
|
12
|
+
result[key] = delete(key) if yield(key, value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Module
|
|
4
|
+
# Shorthand for `const_set(name.camelize, Class.new(parent) { ... })`.
|
|
5
|
+
# Useful when defining classes in macros.
|
|
6
|
+
def define_class(name, *, **macros, &)
|
|
7
|
+
name = name.to_s.camelize
|
|
8
|
+
raise NameError, "class exists: #{name}" if const_defined?(name, false)
|
|
9
|
+
klass = Class.new(*) do
|
|
10
|
+
macros.each { |key, value| send(key, *value) }
|
|
11
|
+
class_eval(&) if block_given?
|
|
12
|
+
end
|
|
13
|
+
const_set name, klass
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/cafe_car/core_ext.rb
CHANGED
data/lib/cafe_car/current.rb
CHANGED
data/lib/cafe_car/engine.rb
CHANGED
|
@@ -6,6 +6,8 @@ require "propshaft"
|
|
|
6
6
|
require "pundit"
|
|
7
7
|
require "importmap-rails"
|
|
8
8
|
require "turbo-rails"
|
|
9
|
+
require "potter"
|
|
10
|
+
require "potter/type"
|
|
9
11
|
|
|
10
12
|
module CafeCar
|
|
11
13
|
class Engine < ::Rails::Engine
|
|
@@ -72,13 +74,18 @@ module CafeCar
|
|
|
72
74
|
end
|
|
73
75
|
end
|
|
74
76
|
|
|
77
|
+
initializer "cafe_car.responders" do
|
|
78
|
+
require "responders"
|
|
79
|
+
config.responders.flash_keys = [ :success, :error ]
|
|
80
|
+
end
|
|
81
|
+
|
|
75
82
|
initializer "cafe_car.field_with_errors" do
|
|
76
83
|
ActionView::Base.field_error_proc = proc { _1.html_safe }
|
|
77
84
|
end
|
|
78
85
|
|
|
79
86
|
initializer "cafe_car.console" do |app|
|
|
80
87
|
app.console do
|
|
81
|
-
TOPLEVEL_BINDING.eval(
|
|
88
|
+
TOPLEVEL_BINDING.eval("self").instance_exec do
|
|
82
89
|
def logger = Rails.logger
|
|
83
90
|
|
|
84
91
|
if defined?(FactoryBot)
|
|
@@ -86,7 +93,7 @@ module CafeCar
|
|
|
86
93
|
logger.info "FactoryBot methods enabled."
|
|
87
94
|
end
|
|
88
95
|
|
|
89
|
-
logger.info
|
|
96
|
+
logger.info "SQL logs enabled."
|
|
90
97
|
|
|
91
98
|
ApplicationController.allow_forgery_protection = false
|
|
92
99
|
logger.info "CSRF disabled to enable app.post calls."
|
|
@@ -29,7 +29,7 @@ module CafeCar
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
-
def add_class(*args, opts) = {class: @template.ui_class([:field, *args], *opts.delete(:class)), **opts}
|
|
32
|
+
def add_class(*args, opts) = { class: @template.ui_class([ :field, *args ], *opts.delete(:class)), **opts }
|
|
33
33
|
|
|
34
34
|
def send_to_form_with_text(method, text = @options.delete(method), **, &)
|
|
35
35
|
return if text == false
|
data/lib/cafe_car/field_info.rb
CHANGED
|
@@ -13,8 +13,9 @@ module CafeCar
|
|
|
13
13
|
|
|
14
14
|
def info(method) = model.info.field(method)
|
|
15
15
|
|
|
16
|
-
def id?
|
|
17
|
-
def constant?
|
|
16
|
+
def id? = method =~ /_ids?$/
|
|
17
|
+
def constant? = method.in? %i[id created_at updated_at]
|
|
18
|
+
def timestamp? = type.in? %i[date datetime time]
|
|
18
19
|
|
|
19
20
|
def association?
|
|
20
21
|
return if @method.nil?
|
|
@@ -30,11 +31,11 @@ module CafeCar
|
|
|
30
31
|
def collection = reflection.klass.all
|
|
31
32
|
def reflection
|
|
32
33
|
return if @method.nil?
|
|
33
|
-
model.reflect_on_association
|
|
34
|
+
model.try(:reflect_on_association, @method) || reflections_by_attribute[@method]
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def abrogated_keys
|
|
37
|
-
[*reflection&.foreign_type&.to_sym]
|
|
38
|
+
[ *reflection&.foreign_type&.to_sym ]
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def displayable = reflection&.name&.then { info(_1) } || self
|
|
@@ -61,7 +62,7 @@ module CafeCar
|
|
|
61
62
|
model.nested_attributes_options.key?(key) && :nested
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
def polymorphic_methods = [reflection.foreign_type, reflection.foreign_key]
|
|
65
|
+
def polymorphic_methods = [ reflection.foreign_type, reflection.foreign_key ]
|
|
65
66
|
|
|
66
67
|
def placeholder = i18n(:placeholder)
|
|
67
68
|
def autocomplete = i18n(:autocomplete)
|
|
@@ -78,7 +79,7 @@ module CafeCar
|
|
|
78
79
|
def i18n_key = model_name.i18n_key
|
|
79
80
|
def i18n(key, **opts)
|
|
80
81
|
return if @method.nil?
|
|
81
|
-
I18n.t(@method, scope: [:helpers, key, i18n_key], raise: true, **opts)
|
|
82
|
+
I18n.t(@method, scope: [ :helpers, key, i18n_key ], raise: true, **opts)
|
|
82
83
|
rescue I18n::MissingTranslationData
|
|
83
84
|
end
|
|
84
85
|
|
|
@@ -95,7 +96,7 @@ module CafeCar
|
|
|
95
96
|
# "minmax(10em, fit-content)"
|
|
96
97
|
# "minmax(10em, 1fr)"
|
|
97
98
|
"minmax(10em, auto)"
|
|
98
|
-
# "min-content"
|
|
99
|
+
# "minmax(min-content, auto)"
|
|
99
100
|
else "min-content"
|
|
100
101
|
end
|
|
101
102
|
end
|
|
@@ -127,13 +128,14 @@ module CafeCar
|
|
|
127
128
|
|
|
128
129
|
@@reflections_by_attribute = {}
|
|
129
130
|
def reflections_by_attribute
|
|
131
|
+
return {} unless model.respond_to? :reflections
|
|
130
132
|
@@reflections_by_attribute[model] ||=
|
|
131
133
|
model.reflections.values.index_by do |r|
|
|
132
|
-
case [r.macro, r.name]
|
|
133
|
-
in [:belongs_to, *] then r.foreign_key
|
|
134
|
-
in [:has_many, *] then "#{r.name.to_s.singularize}_ids"
|
|
135
|
-
in [:has_one, /^rich_text_(\w+)$/] then $1
|
|
136
|
-
in [:has_one, *] then r.name
|
|
134
|
+
case [ r.macro, r.name ]
|
|
135
|
+
in [ :belongs_to, * ] then r.foreign_key
|
|
136
|
+
in [ :has_many, * ] then "#{r.name.to_s.singularize}_ids"
|
|
137
|
+
in [ :has_one, /^rich_text_(\w+)$/ ] then $1
|
|
138
|
+
in [ :has_one, * ] then r.name
|
|
137
139
|
else raise NoMethodError.new("Not yet implemented :#{r.macro}")
|
|
138
140
|
end
|
|
139
141
|
end.with_indifferent_access
|
data/lib/cafe_car/fields.rb
CHANGED
|
@@ -5,10 +5,17 @@ module CafeCar
|
|
|
5
5
|
derive :editable, -> { Fields.new reject(&:constant?) }
|
|
6
6
|
derive :listable, -> { Fields.new editable.reject(&:digest?) }
|
|
7
7
|
derive :attachments, -> { Fields.new select(&:attachment?) }
|
|
8
|
+
derive :timestamps, -> { Fields.new select(&:timestamp?) }
|
|
8
9
|
|
|
9
10
|
derive :by_name, -> { index_by(&:name).with_indifferent_access }
|
|
10
11
|
derive :names, -> { map(&:name) }
|
|
11
12
|
|
|
13
|
+
def reverse = Fields.new(super)
|
|
14
|
+
|
|
15
|
+
def sort_with(obj)
|
|
16
|
+
Fields.new(sort_by { obj.try(_1.name) })
|
|
17
|
+
end
|
|
18
|
+
|
|
12
19
|
def has?(name) = by_name.key?(name)
|
|
13
20
|
end
|
|
14
21
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class CafeCar::Filter::FieldInfo < CafeCar[:FieldInfo]
|
|
2
2
|
def i18n(key, **opts)
|
|
3
|
-
I18n.t(@method, scope: [:helpers, :filter, key, i18n_key], raise: true, **opts)
|
|
3
|
+
I18n.t(@method, scope: [ :helpers, :filter, key, i18n_key ], raise: true, **opts)
|
|
4
4
|
rescue I18n::MissingTranslationData
|
|
5
5
|
end
|
|
6
6
|
|
|
@@ -10,12 +10,12 @@ module CafeCar::Filter
|
|
|
10
10
|
dotted_name method
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def clean(method) = method.to_s.sub(/^\W+|\W+$/,
|
|
13
|
+
def clean(method) = method.to_s.sub(/^\W+|\W+$/, "")
|
|
14
14
|
def info(method) = super(clean(method))
|
|
15
15
|
|
|
16
16
|
def field_name(*methods, multiple: false, index: @options[:index])
|
|
17
17
|
# TODO: handle multiple/index
|
|
18
|
-
["", *methods].join(".")
|
|
18
|
+
[ "", *methods ].join(".")
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|