active_element 0.0.13 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +16 -31
  3. data/app/assets/javascripts/active_element/form.js +2 -1
  4. data/app/assets/javascripts/active_element/json_field.js +8 -1
  5. data/app/views/active_element/_title.html.erb +1 -0
  6. data/app/views/active_element/components/button.html.erb +6 -4
  7. data/app/views/active_element/components/form/_check_boxes.html.erb +6 -4
  8. data/app/views/active_element/components/form/_date_range_field.html.erb +14 -0
  9. data/app/views/active_element/components/form/_field.html.erb +3 -0
  10. data/app/views/active_element/components/form/_summary.html.erb +10 -0
  11. data/app/views/active_element/components/form.html.erb +12 -2
  12. data/app/views/active_element/components/table/_collection_row.html.erb +3 -3
  13. data/app/views/active_element/components/table/_field.html.erb +2 -2
  14. data/app/views/active_element/components/table/collection.html.erb +20 -11
  15. data/app/views/active_element/components/table/item.html.erb +4 -0
  16. data/app/views/active_element/default_views/forbidden.html.erb +17 -3
  17. data/app/views/active_element/default_views/index.html.erb +14 -6
  18. data/app/views/layouts/active_element.html.erb +4 -2
  19. data/config/locales/en.yml +3 -0
  20. data/example_app/.ruby-version +1 -1
  21. data/example_app/Gemfile +0 -2
  22. data/example_app/Gemfile.lock +5 -4
  23. data/lib/active_element/components/button.rb +6 -4
  24. data/lib/active_element/components/collection_table.rb +10 -3
  25. data/lib/active_element/components/form.rb +27 -2
  26. data/lib/active_element/components/item_table.rb +5 -3
  27. data/lib/active_element/components/navbar.rb +1 -1
  28. data/lib/active_element/components/util/association_mapping.rb +50 -26
  29. data/lib/active_element/components/util/default_display_value.rb +51 -0
  30. data/lib/active_element/components/util/display_value_mapping.rb +10 -0
  31. data/lib/active_element/components/util/field_mapping.rb +2 -2
  32. data/lib/active_element/components/util/form_field_mapping.rb +68 -15
  33. data/lib/active_element/components/util/record_mapping.rb +24 -3
  34. data/lib/active_element/components/util/record_path.rb +51 -20
  35. data/lib/active_element/components/util.rb +13 -0
  36. data/lib/active_element/controller_interface.rb +11 -1
  37. data/lib/active_element/controller_state.rb +4 -2
  38. data/lib/active_element/default_controller/params.rb +9 -1
  39. data/lib/active_element/default_controller/search.rb +22 -16
  40. data/lib/active_element/field_options.rb +20 -0
  41. data/lib/active_element/version.rb +1 -1
  42. data/lib/active_element.rb +1 -0
  43. data/rspec-documentation/pages/016-Default Controller.md +10 -1
  44. metadata +6 -2
@@ -8,6 +8,7 @@ require_relative 'util/form_field_mapping'
8
8
  require_relative 'util/form_value_mapping'
9
9
  require_relative 'util/display_value_mapping'
10
10
  require_relative 'util/association_mapping'
11
+ require_relative 'util/default_display_value'
11
12
  require_relative 'util/decorator'
12
13
  require_relative 'util/numeric_field'
13
14
 
@@ -23,12 +24,24 @@ module ActiveElement
23
24
  record&.try(:model_name)&.try(&:singular) || default_record_name(record)
24
25
  end
25
26
 
27
+ # TODO: Remove and use .sti_record_names everywhere instead.
26
28
  def self.sti_record_name(record)
27
29
  return default_record_name(record) unless record.class.respond_to?(:inheritance_column)
28
30
 
29
31
  record&.class&.superclass&.model_name&.singular if record&.try(record.class.inheritance_column).present?
30
32
  end
31
33
 
34
+ def self.sti_record_names(record) # rubocop:disable Metrics/CyclomaticComplexity
35
+ record.class.ancestors.select do |ancestor|
36
+ next false if ancestor == record.class
37
+ next false if ancestor.try(:inheritance_column).blank?
38
+ next false unless ancestor < ActiveRecord::Base
39
+ next false if ancestor.abstract_class?
40
+
41
+ true
42
+ end.map(&:model_name).map(&:singular)
43
+ end
44
+
32
45
  def self.default_record_name(record)
33
46
  (record.is_a?(Class) ? record.name : record.class.name).demodulize.underscore
34
47
  end
@@ -38,10 +38,16 @@ module ActiveElement
38
38
  state.editable_fields.concat(args.map(&:to_sym)).uniq!
39
39
  end
40
40
 
41
- def searchable_fields(*args)
41
+ def searchable_fields(*args, required: false)
42
42
  state.searchable_fields.concat(args.map(&:to_sym)).uniq!
43
+ state.search_required = required
43
44
  end
44
45
 
46
+ def field_options(field, &block)
47
+ state.field_options[field] = block
48
+ end
49
+ alias field field_options
50
+
45
51
  def deletable
46
52
  state.deletable = true
47
53
  end
@@ -50,6 +56,10 @@ module ActiveElement
50
56
  RailsComponent.new(::Rails).application_name
51
57
  end
52
58
 
59
+ def t(identifier, **kwargs)
60
+ ::I18n.t("active_element.#{identifier}", **kwargs)
61
+ end
62
+
53
63
  def authenticate_with(&block)
54
64
  state.authenticator = block
55
65
  end
@@ -5,9 +5,10 @@ module ActiveElement
5
5
  # configuration. Used throughout ActiveElement for generating dynamic content based on
6
6
  # controller configuration.
7
7
  class ControllerState
8
- attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields
8
+ attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields,
9
+ :field_options
9
10
  attr_accessor :sign_in_path, :sign_in, :sign_in_method, :sign_out_path, :sign_out_method,
10
- :deletable, :authorizor, :authenticator, :list_order
11
+ :deletable, :authorizor, :authenticator, :list_order, :search_required
11
12
 
12
13
  def initialize(controller:)
13
14
  @controller = controller
@@ -19,6 +20,7 @@ module ActiveElement
19
20
  @viewable_fields = []
20
21
  @editable_fields = []
21
22
  @searchable_fields = []
23
+ @field_options = {}
22
24
  end
23
25
 
24
26
  def deletable?
@@ -33,7 +33,15 @@ module ActiveElement
33
33
  scalar, json = controller.active_element.state.editable_fields.partition do |field|
34
34
  scalar?(field)
35
35
  end
36
- scalar + [json_params(json)]
36
+ scalar + relation_fields + [json_params(json)]
37
+ end
38
+
39
+ def relation_fields
40
+ controller.active_element.state.editable_fields.map do |field|
41
+ next nil unless relation?(field)
42
+
43
+ relation(field).try(:foreign_key)
44
+ end.compact
37
45
  end
38
46
 
39
47
  def scalar?(field)
@@ -25,9 +25,11 @@ module ActiveElement
25
25
  conditions = search_filters.to_h.map do |key, value|
26
26
  next relation_matches(key, value) if relation?(key)
27
27
  next datetime_between(key, value) if datetime?(key)
28
+ next model.arel_table[key].matches("#{value}%") if string_like_column?(key)
28
29
 
29
- model.arel_table[key].matches("#{value}%")
30
+ model.arel_table[key].eq(value)
30
31
  end
32
+
31
33
  conditions[1..].reduce(conditions.first) do |accumulated, condition|
32
34
  accumulated.and(condition)
33
35
  end
@@ -41,12 +43,27 @@ module ActiveElement
41
43
 
42
44
  attr_reader :controller, :model
43
45
 
46
+ def string_like_column?(key)
47
+ [:string, :text].include?(
48
+ model.columns.find { |column| column.name.to_s == key.to_s }&.type&.to_sym
49
+ )
50
+ end
51
+
44
52
  def searchable_fields
45
- controller.active_element.state.searchable_fields.map do |field|
53
+ fields = controller.active_element.state.searchable_fields.map do |field|
46
54
  next field unless field.to_s.end_with?('_at')
47
55
 
48
56
  { field => %i[from to] }
49
57
  end
58
+ (fields + relation_fields).uniq
59
+ end
60
+
61
+ def relation_fields
62
+ controller.active_element.state.searchable_fields.map do |field|
63
+ next nil unless relation?(field)
64
+
65
+ relation(field).try(:foreign_key)
66
+ end.compact
50
67
  end
51
68
 
52
69
  def noop
@@ -76,21 +93,10 @@ module ActiveElement
76
93
  end
77
94
 
78
95
  def relation_matches(key, value)
79
- fields = searchable_relation_fields(key)
80
- relation_model = relation(key).klass
81
- fields.select! do |field|
82
- relation_model.columns.find { |column| column.name.to_s == field.to_s }&.type == :string
83
- end
84
-
85
- return noop if fields.empty?
96
+ foreign_key = relation(key).try(:foreign_key)
97
+ return noop unless foreign_key.present?
86
98
 
87
- relation_conditions(fields, value, relation_model)
88
- end
89
-
90
- def relation_conditions(fields, value, relation_model)
91
- fields[1..].reduce(relation_model.arel_table[fields.first].matches("#{value}%")) do |condition, field|
92
- condition.or(relation_model.arel_table[field].matches("#{value}%"))
93
- end
99
+ model.arel_table[foreign_key].eq(value)
94
100
  end
95
101
 
96
102
  def searchable_relation_fields(key)
@@ -0,0 +1,20 @@
1
+ module ActiveElement
2
+ class FieldOptions
3
+ attr_accessor :type, :options
4
+ attr_reader :field
5
+
6
+ def self.from_state(field, state, record)
7
+ block = state.field_options[field]
8
+ return nil if block.blank?
9
+
10
+ field_options = new(field)
11
+ block.call(field_options, record)
12
+ field_options
13
+ end
14
+
15
+ def initialize(field)
16
+ @field = field
17
+ @options = {}
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveElement
4
- VERSION = '0.0.13'
4
+ VERSION = '0.0.15'
5
5
  end
@@ -14,6 +14,7 @@ require_relative 'active_element/permissions_report'
14
14
  require_relative 'active_element/controller_interface'
15
15
  require_relative 'active_element/controller_state'
16
16
  require_relative 'active_element/controller_action'
17
+ require_relative 'active_element/field_options'
17
18
  require_relative 'active_element/default_controller'
18
19
  require_relative 'active_element/pre_render_processors'
19
20
  require_relative 'active_element/rails_component'
@@ -105,7 +105,7 @@ The results will be rendered in a horizontal table with one row for each item, i
105
105
  # app/controllers/restaurants_controller.rb
106
106
 
107
107
  class RestaurantsController < ApplicationController
108
- active_element.searchable_fields :name, :restaurateur, :created_at
108
+ active_element.viewable_fields :name, :restaurateur, :created_at
109
109
  end
110
110
  ```
111
111
 
@@ -119,6 +119,15 @@ Note that each field type can be overridden and configured by defining `config/f
119
119
 
120
120
  By default, `json` and `jsonb` fields use the [JSON Field](form-fields/json.html) type, allowing you to edit complex _JSON_ data structures via user-friendly _HTML_ forms. A [schema file](form-fields/json/schema.html) **must** be defined for these fields. See the `json_field` documentation for more details.
121
121
 
122
+
123
+ ```ruby
124
+ # app/controllers/restaurants_controller.rb
125
+
126
+ class RestaurantsController < ApplicationController
127
+ active_element.editable_fields :name, :restaurateur
128
+ end
129
+ ```
130
+
122
131
  ## Deletable
123
132
 
124
133
  The `active_element.deletable` declaration does not receive any arguments but specifies that a record can be deleted by a user.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_element
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-22 00:00:00.000000000 Z
11
+ date: 2023-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bootstrap
@@ -131,6 +131,7 @@ files:
131
131
  - app/assets/stylesheets/active_element/application.scss
132
132
  - app/controllers/active_element/application_controller.rb
133
133
  - app/controllers/concerns/active_element/default_controller_actions.rb
134
+ - app/views/active_element/_title.html.erb
134
135
  - app/views/active_element/_user.html.erb
135
136
  - app/views/active_element/components/_horizontal_tabs.html.erb
136
137
  - app/views/active_element/components/_vertical_tabs.html.erb
@@ -140,6 +141,7 @@ files:
140
141
  - app/views/active_element/components/form.html.erb
141
142
  - app/views/active_element/components/form/_check_box.html.erb
142
143
  - app/views/active_element/components/form/_check_boxes.html.erb
144
+ - app/views/active_element/components/form/_date_range_field.html.erb
143
145
  - app/views/active_element/components/form/_datetime_range_field.html.erb
144
146
  - app/views/active_element/components/form/_field.html.erb
145
147
  - app/views/active_element/components/form/_generic_field.html.erb
@@ -307,6 +309,7 @@ files:
307
309
  - lib/active_element/components/util.rb
308
310
  - lib/active_element/components/util/association_mapping.rb
309
311
  - lib/active_element/components/util/decorator.rb
312
+ - lib/active_element/components/util/default_display_value.rb
310
313
  - lib/active_element/components/util/display_value_mapping.rb
311
314
  - lib/active_element/components/util/field_mapping.rb
312
315
  - lib/active_element/components/util/form_field_mapping.rb
@@ -325,6 +328,7 @@ files:
325
328
  - lib/active_element/default_controller/params.rb
326
329
  - lib/active_element/default_controller/search.rb
327
330
  - lib/active_element/engine.rb
331
+ - lib/active_element/field_options.rb
328
332
  - lib/active_element/json_field_schema.rb
329
333
  - lib/active_element/permissions_check.rb
330
334
  - lib/active_element/permissions_report.rb