active_element 0.0.11 → 0.0.13

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +2 -1
  4. data/Gemfile.lock +10 -3
  5. data/app/assets/javascripts/active_element/highlight.js +311 -0
  6. data/app/assets/javascripts/active_element/json_field.js +51 -20
  7. data/app/assets/javascripts/active_element/popover.js +6 -4
  8. data/app/assets/javascripts/active_element/text_search_field.js +13 -1
  9. data/app/assets/stylesheets/active_element/_dark.scss +1 -1
  10. data/app/assets/stylesheets/active_element/application.scss +39 -1
  11. data/app/controllers/concerns/active_element/default_controller_actions.rb +7 -7
  12. data/app/views/active_element/components/fields/_json.html.erb +3 -2
  13. data/app/views/active_element/components/form/_field.html.erb +2 -1
  14. data/app/views/active_element/components/form/_json.html.erb +2 -0
  15. data/app/views/active_element/components/form/_label.html.erb +8 -1
  16. data/app/views/active_element/components/form/_templates.html.erb +8 -5
  17. data/app/views/active_element/components/form/_text_search.html.erb +1 -1
  18. data/app/views/active_element/components/form.html.erb +2 -2
  19. data/app/views/active_element/components/table/_field.html.erb +1 -1
  20. data/app/views/active_element/components/table/_ungrouped_collection.html.erb +1 -0
  21. data/app/views/active_element/components/table/item.html.erb +1 -0
  22. data/app/views/active_element/default_views/edit.html.erb +2 -2
  23. data/app/views/active_element/default_views/forbidden.html.erb +7 -0
  24. data/app/views/active_element/default_views/index.html.erb +7 -7
  25. data/app/views/active_element/default_views/new.html.erb +1 -1
  26. data/app/views/active_element/default_views/show.html.erb +3 -3
  27. data/app/views/layouts/active_element.html.erb +25 -4
  28. data/config/locales/en.yml +3 -0
  29. data/example_app/Gemfile.lock +1 -1
  30. data/example_app/app/controllers/pets_controller.rb +1 -0
  31. data/example_app/app/controllers/users_controller.rb +1 -0
  32. data/lib/active_element/components/form.rb +1 -8
  33. data/lib/active_element/components/text_search.rb +10 -1
  34. data/lib/active_element/components/util/display_value_mapping.rb +0 -2
  35. data/lib/active_element/components/util/form_field_mapping.rb +23 -10
  36. data/lib/active_element/components/util/numeric_field.rb +73 -0
  37. data/lib/active_element/components/util.rb +8 -0
  38. data/lib/active_element/controller_interface.rb +27 -30
  39. data/lib/active_element/controller_state.rb +44 -0
  40. data/lib/active_element/default_controller/actions.rb +3 -0
  41. data/lib/active_element/default_controller/controller.rb +145 -0
  42. data/lib/active_element/default_controller/json_params.rb +48 -0
  43. data/lib/active_element/default_controller/params.rb +97 -0
  44. data/lib/active_element/default_controller/search.rb +112 -0
  45. data/lib/active_element/default_controller.rb +10 -88
  46. data/lib/active_element/version.rb +1 -1
  47. data/lib/active_element.rb +1 -2
  48. data/rspec-documentation/_head.html.erb +2 -0
  49. data/rspec-documentation/pages/000-Introduction.md +8 -5
  50. data/rspec-documentation/pages/005-Setup.md +21 -28
  51. data/rspec-documentation/pages/010-Components/Form Fields.md +35 -0
  52. data/rspec-documentation/pages/015-Custom Controllers.md +32 -0
  53. data/rspec-documentation/pages/016-Default Controller.md +132 -0
  54. data/rspec-documentation/pages/Themes.md +3 -0
  55. metadata +15 -4
  56. data/lib/active_element/default_record_params.rb +0 -62
  57. data/lib/active_element/default_text_search.rb +0 -110
@@ -16,7 +16,7 @@ module ActiveElement
16
16
  def initialize(controller_class, controller_instance = nil)
17
17
  @controller_class = controller_class
18
18
  @controller_instance = controller_instance
19
- initialize_state
19
+ initialize_state(controller_class)
20
20
  @missing_template_store = {}
21
21
  @authorize = false
22
22
  end
@@ -25,20 +25,25 @@ module ActiveElement
25
25
  @authorize
26
26
  end
27
27
 
28
- def listable_fields(*args)
29
- state[:listable_fields] = args.map(&:to_sym)
28
+ def listable_fields(*args, order: nil)
29
+ state.list_order = order
30
+ state.listable_fields.concat(args.map(&:to_sym)).uniq!
30
31
  end
31
32
 
32
33
  def viewable_fields(*args)
33
- state[:viewable_fields] = args.map(&:to_sym)
34
+ state.viewable_fields.concat(args.map(&:to_sym)).uniq!
34
35
  end
35
36
 
36
37
  def editable_fields(*args)
37
- state[:editable_fields] = args.map(&:to_sym)
38
+ state.editable_fields.concat(args.map(&:to_sym)).uniq!
38
39
  end
39
40
 
40
41
  def searchable_fields(*args)
41
- state[:searchable_fields] = args.map(&:to_sym)
42
+ state.searchable_fields.concat(args.map(&:to_sym)).uniq!
43
+ end
44
+
45
+ def deletable
46
+ state.deletable = true
42
47
  end
43
48
 
44
49
  def application_name
@@ -46,43 +51,39 @@ module ActiveElement
46
51
  end
47
52
 
48
53
  def authenticate_with(&block)
49
- state[:authenticator] = block
54
+ state.authenticator = block
50
55
  end
51
56
 
52
57
  def authorize_with(&block)
53
58
  @authorize = true
54
- state[:authorizor] = block
59
+ state.authorizor = block
55
60
  end
56
61
 
57
62
  def sign_out_with(method: :get, &block)
58
- state[:sign_out_method] = method
59
- state[:sign_out_path] = block
63
+ state.sign_out_method = method
64
+ state.sign_out_path = block
60
65
  end
61
66
 
62
67
  def sign_out_path
63
- state[:sign_out_path]&.call
68
+ state.sign_out_path&.call
64
69
  end
65
70
 
66
- def sign_out_method
67
- state[:sign_out_method]
68
- end
71
+ delegate :sign_out_method, to: :state
69
72
 
70
73
  def sign_in_with(method: :get, &block)
71
- state[:sign_in_method] = method
72
- state[:sign_in_path] = block
74
+ state.sign_in_method = method
75
+ state.sign_in_path = block
73
76
  end
74
77
 
75
78
  def sign_in_path
76
- state[:sign_in_path]&.call
79
+ state.sign_in_path&.call
77
80
  end
78
81
 
79
- def sign_in_method
80
- state[:sign_in_method]
81
- end
82
+ delegate :sign_in_method, to: :state
82
83
 
83
84
  def authenticate
84
85
  authenticator&.call
85
- @current_user = state[:authorizor]&.call
86
+ @current_user = state.authorizor&.call
86
87
 
87
88
  nil
88
89
  end
@@ -91,16 +92,12 @@ module ActiveElement
91
92
  raise ArgumentError, "Must specify `with: '<permission>'` or `always: true`" unless with.present? || always
92
93
  raise ArgumentError, 'Cannot specify both `with` and `always: true`' if with.present? && always
93
94
 
94
- state[:permissions] << { with: with, always: always, action: action }
95
+ state.permissions << { with: with, always: always, action: action }
95
96
  end
96
97
 
97
- def authenticator
98
- state[:authenticator]
99
- end
98
+ delegate :authenticator, to: :state
100
99
 
101
- def permissions
102
- state.fetch(:permissions)
103
- end
100
+ delegate :permissions, to: :state
104
101
 
105
102
  def component
106
103
  return (@component ||= ActiveElement::Component.new(controller_instance)) unless controller_instance.nil?
@@ -116,8 +113,8 @@ module ActiveElement
116
113
 
117
114
  attr_reader :controller_class, :controller_instance
118
115
 
119
- def initialize_state
120
- self.class.state[controller_class] ||= { permissions: [], authenticator: nil }
116
+ def initialize_state(key)
117
+ self.class.state[key] ||= ControllerState.new(controller: controller_instance)
121
118
  end
122
119
  end
123
120
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveElement
4
+ # Stores various data for a controller, including various field definitions and authentication
5
+ # configuration. Used throughout ActiveElement for generating dynamic content based on
6
+ # controller configuration.
7
+ class ControllerState
8
+ attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields
9
+ attr_accessor :sign_in_path, :sign_in, :sign_in_method, :sign_out_path, :sign_out_method,
10
+ :deletable, :authorizor, :authenticator, :list_order
11
+
12
+ def initialize(controller:)
13
+ @controller = controller
14
+ @permissions = []
15
+ @authenticator = nil
16
+ @authorizor = nil
17
+ @deletable = false
18
+ @listable_fields = []
19
+ @viewable_fields = []
20
+ @editable_fields = []
21
+ @searchable_fields = []
22
+ end
23
+
24
+ def deletable?
25
+ !!deletable
26
+ end
27
+
28
+ def viewable?
29
+ viewable_fields.present? || controller.public_methods(false).include?(:show)
30
+ end
31
+
32
+ def editable?
33
+ editable_fields.present? || controller.public_methods(false).include?(:edit)
34
+ end
35
+
36
+ def creatable?
37
+ editable_fields.present? || controller.public_methods(false).include?(:new)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :controller
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ # TODO: Move each default controller action into individual classes inside
3
+ # ActiveElement::DefaultController::Actions
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveElement
4
+ module DefaultController
5
+ # Encapsulation of all logic performed for default controller actions when no action is defined
6
+ # by the current controller.
7
+ class Controller # rubocop:disable Metrics/ClassLength
8
+ def initialize(controller:)
9
+ @controller = controller
10
+ end
11
+
12
+ def index
13
+ return render_forbidden(:listable) unless configured?(:listable)
14
+
15
+ controller.render 'active_element/default_views/index',
16
+ locals: {
17
+ collection: ordered(collection),
18
+ search_filters: default_text_search.search_filters
19
+ }
20
+ end
21
+
22
+ def show
23
+ return render_forbidden(:viewable) unless configured?(:viewable)
24
+
25
+ controller.render 'active_element/default_views/show', locals: { record: record }
26
+ end
27
+
28
+ def new
29
+ return render_forbidden(:editable) unless configured?(:editable)
30
+
31
+ controller.render 'active_element/default_views/new', locals: { record: model.new, namespace: namespace }
32
+ end
33
+
34
+ def create # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
35
+ return render_forbidden(:editable) unless configured?(:editable)
36
+
37
+ new_record = model.new(default_record_params.params)
38
+ # XXX: Ensure associations are applied - there must be a better way.
39
+ if new_record.save && new_record.reload.update(default_record_params.params)
40
+ controller.flash.notice = "#{new_record.model_name.to_s.titleize} created successfully."
41
+ controller.redirect_to record_path(new_record, :show).path
42
+ else
43
+ controller.flash.now.alert = "Failed to create #{model.name.to_s.titleize}."
44
+ controller.render 'active_element/default_views/new', locals: { record: new_record, namespace: namespace }
45
+ end
46
+ rescue ActiveRecord::RangeError => e
47
+ render_range_error(error: e, action: :new)
48
+ end
49
+
50
+ def edit
51
+ return render_forbidden(:editable) unless configured?(:editable)
52
+
53
+ controller.render 'active_element/default_views/edit', locals: { record: record, namespace: namespace }
54
+ end
55
+
56
+ def update # rubocop:disable Metrics/AbcSize
57
+ return render_forbidden(:editable) unless configured?(:editable)
58
+
59
+ if record.update(default_record_params.params)
60
+ controller.flash.notice = "#{record.model_name.to_s.titleize} updated successfully."
61
+ controller.redirect_to record_path(record, :show).path
62
+ else
63
+ controller.flash.now.alert = "Failed to update #{model.name.to_s.titleize}."
64
+ controller.render 'active_element/default_views/edit', locals: { record: record, namespace: namespace }
65
+ end
66
+ rescue ActiveRecord::RangeError => e
67
+ render_range_error(error: e, action: :edit)
68
+ end
69
+
70
+ def destroy
71
+ return render_forbidden(:deletable) unless configured?(:deletable)
72
+
73
+ record.destroy
74
+ controller.flash.notice = "Deleted #{record.model_name.to_s.titleize}."
75
+ controller.redirect_to record_path(model, :index).path
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :controller
81
+
82
+ def ordered(unordered_collection)
83
+ return unordered_collection if state.list_order.blank?
84
+
85
+ unordered_collection.order(state.list_order)
86
+ end
87
+
88
+ def render_forbidden(type)
89
+ controller.render 'active_element/default_views/forbidden', locals: { type: type }
90
+ end
91
+
92
+ def configured?(type)
93
+ return state.deletable? if type == :deletable
94
+
95
+ state.public_send("#{type}_fields").present?
96
+ end
97
+
98
+ def state
99
+ @state ||= controller.active_element.state
100
+ end
101
+
102
+ def default_record_params
103
+ @default_record_params ||= DefaultController::Params.new(controller: controller, model: model)
104
+ end
105
+
106
+ def default_text_search
107
+ @default_text_search ||= DefaultController::Search.new(controller: controller, model: model)
108
+ end
109
+
110
+ def record_path(record, type = nil)
111
+ ActiveElement::Components::Util::RecordPath.new(record: record, controller: controller, type: type)
112
+ end
113
+
114
+ def namespace
115
+ controller.controller_path.rpartition('/').first.presence&.to_sym
116
+ end
117
+
118
+ def model
119
+ controller.controller_name.classify.constantize
120
+ end
121
+
122
+ def record
123
+ @record ||= model.find(controller.params[:id])
124
+ end
125
+
126
+ def collection
127
+ return model.all unless default_text_search.text_search?
128
+
129
+ model.left_outer_joins(default_text_search.search_relations).where(*default_text_search.text_search)
130
+ end
131
+
132
+ def render_range_error(error:, action:)
133
+ controller.flash.now.alert = formatted_error(error)
134
+ controller.render "active_element/default_views/#{action}", locals: { record: record, namespace: namespace }
135
+ end
136
+
137
+ def formatted_error(error)
138
+ return error.cause.message.split("\n").join(', ') if error.try(:cause)&.try(:message).present?
139
+ return error.message if error.try(:message).present?
140
+
141
+ I18n.t('active_element.unexpected_error')
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveElement
4
+ module DefaultController
5
+ # Provides permitted parameters for fields generated from a JSON schema file.
6
+ class JsonParams
7
+ def initialize(schema:)
8
+ @base_schema = schema
9
+ end
10
+
11
+ def params(schema = base_schema)
12
+ return simple_object_field(schema) if simple_object_field?(schema)
13
+ return simple_array_field(schema) if simple_array_field?(schema)
14
+ return complex_array_field(schema) if complex_array_field?(schema)
15
+
16
+ schema[:name]
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :fields, :base_schema
22
+
23
+ def simple_object_field(schema)
24
+ schema.key?(:name) ? { schema[:name] => {} } : {}
25
+ end
26
+
27
+ def simple_array_field(schema)
28
+ schema.key?(:name) ? { schema[:name] => [] } : []
29
+ end
30
+
31
+ def simple_object_field?(schema)
32
+ schema[:type] == 'object'
33
+ end
34
+
35
+ def simple_array_field?(schema)
36
+ schema[:type] == 'array' && schema.dig(:shape, :type) != 'object'
37
+ end
38
+
39
+ def complex_array_field?(schema)
40
+ schema[:type] == 'array' && schema.dig(:shape, :type) == 'object'
41
+ end
42
+
43
+ def complex_array_field(schema)
44
+ schema.dig(:shape, :shape, :fields).map { |field| params(field) }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveElement
4
+ module DefaultController
5
+ # Provides params for ActiveRecord models when using the default boilerplate controller
6
+ # actions. Navigates input parameters and maps them to appropriate relations as needed.
7
+ class Params
8
+ def initialize(controller:, model:)
9
+ @controller = controller
10
+ @model = model
11
+ end
12
+
13
+ def params
14
+ with_transformed_relations(
15
+ controller.params.require(controller.controller_name.singularize)
16
+ .permit(*permitted_fields)
17
+ )
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :controller, :model
23
+
24
+ def with_transformed_relations(params)
25
+ params.to_h.to_h do |key, value|
26
+ next [key, value] unless relation?(key)
27
+
28
+ relation_param(key, value)
29
+ end
30
+ end
31
+
32
+ def permitted_fields
33
+ scalar, json = controller.active_element.state.editable_fields.partition do |field|
34
+ scalar?(field)
35
+ end
36
+ scalar + [json_params(json)]
37
+ end
38
+
39
+ def scalar?(field)
40
+ return true if relation?(field)
41
+ return true if %i[json jsonb].exclude?(column(field)&.type)
42
+
43
+ false
44
+ end
45
+
46
+ def json_params(fields)
47
+ # XXX: We assume all non-scalar fields are JSON fields, i.e. they must have a definition
48
+ # defined as `config/forms/<model>/<field>.yml`. If that file does not exist, allow
49
+ # Errno::ENOENT to raise to let the form submission fail and avoid losing data. This
50
+ # would need to be adjusted if we start allowing non-JSON nested fields in the default
51
+ # controller.
52
+ fields.index_with do |field|
53
+ DefaultController::JsonParams.new(schema: schema_for(field)).params
54
+ end
55
+ end
56
+
57
+ def schema_for(field)
58
+ ActiveElement::Components::Util.json_schema(model: model, field: field)
59
+ end
60
+
61
+ def relation_param(key, value)
62
+ case relation(key).macro
63
+ when :belongs_to
64
+ belongs_to_param(key, value)
65
+ when :has_one
66
+ has_one_param(key, value)
67
+ when :has_many
68
+ has_many_param(key, value)
69
+ end
70
+ end
71
+
72
+ def belongs_to_param(key, value)
73
+ [relation(key).foreign_key, value]
74
+ end
75
+
76
+ def has_one_param(key, value) # rubocop:disable Naming/PredicateName
77
+ [relation(key).name, relation(key).klass.find_by(relation(key).klass.primary_key => value)]
78
+ end
79
+
80
+ def has_many_param(key, _value) # rubocop:disable Naming/PredicateName
81
+ [relation(key).name, relation(key).klass.where(relation(key).klass.primary_key => relation(key).value)]
82
+ end
83
+
84
+ def relation?(attribute)
85
+ relation(attribute.to_sym).present?
86
+ end
87
+
88
+ def relation(attribute)
89
+ model.reflect_on_association(attribute.to_sym)
90
+ end
91
+
92
+ def column(attribute)
93
+ model.columns.find { |column| column.name.to_s == attribute.to_s }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveElement
4
+ module DefaultController
5
+ # Full text search and datetime querying for DefaultController, provides full text search
6
+ # filters for all controllers with configured searchable fields. Includes support for querying
7
+ # across relations.
8
+ class Search
9
+ def initialize(controller:, model:)
10
+ @controller = controller
11
+ @model = model
12
+ end
13
+
14
+ def search_filters
15
+ @search_filters ||= controller.params.permit(*searchable_fields).transform_values do |value|
16
+ value.try(:compact_blank) || value
17
+ end.compact_blank
18
+ end
19
+
20
+ def text_search?
21
+ search_filters.present?
22
+ end
23
+
24
+ def text_search
25
+ conditions = search_filters.to_h.map do |key, value|
26
+ next relation_matches(key, value) if relation?(key)
27
+ next datetime_between(key, value) if datetime?(key)
28
+
29
+ model.arel_table[key].matches("#{value}%")
30
+ end
31
+ conditions[1..].reduce(conditions.first) do |accumulated, condition|
32
+ accumulated.and(condition)
33
+ end
34
+ end
35
+
36
+ def search_relations
37
+ search_filters.to_h.keys.map { |key| relation?(key) ? key.to_sym : nil }.compact
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :controller, :model
43
+
44
+ def searchable_fields
45
+ controller.active_element.state.searchable_fields.map do |field|
46
+ next field unless field.to_s.end_with?('_at')
47
+
48
+ { field => %i[from to] }
49
+ end
50
+ end
51
+
52
+ def noop
53
+ Arel::Nodes::True.new.eq(Arel::Nodes::True.new)
54
+ end
55
+
56
+ def datetime?(key)
57
+ model.columns.find { |column| column.name.to_s == key.to_s }&.type == :datetime
58
+ end
59
+
60
+ def datetime_between(key, value)
61
+ return noop if value[:from].blank? && value[:to].blank?
62
+
63
+ model.arel_table[key].between(range_begin(value)...range_end(value))
64
+ end
65
+
66
+ def range_begin(value)
67
+ value[:from].present? ? Time.zone.parse(value[:from]) + timezone_offset : -Float::INFINITY
68
+ end
69
+
70
+ def range_end(value)
71
+ value[:to].present? ? Time.zone.parse(value[:to]) + timezone_offset : Float::INFINITY
72
+ end
73
+
74
+ def timezone_offset
75
+ controller.request.cookies['timezone_offset'].to_i.minutes
76
+ end
77
+
78
+ 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?
86
+
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
94
+ end
95
+
96
+ def searchable_relation_fields(key)
97
+ Components::Util.relation_controller(model, controller, key)
98
+ &.active_element
99
+ &.state
100
+ &.fetch(:searchable_fields, []) || []
101
+ end
102
+
103
+ def relation?(attribute)
104
+ relation(attribute.to_sym).present?
105
+ end
106
+
107
+ def relation(attribute)
108
+ model.reflect_on_association(attribute.to_sym)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,93 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveElement
4
- # Encapsulation of all logic performed for default controller actions when no action is defined
5
- # by the current controller.
6
- class DefaultController
7
- def initialize(controller:)
8
- @controller = controller
9
- end
10
-
11
- def index
12
- controller.render 'active_element/default_views/index',
13
- locals: {
14
- collection: collection,
15
- search_filters: default_text_search.search_filters
16
- }
17
- end
18
-
19
- def show
20
- controller.render 'active_element/default_views/show', locals: { record: record }
21
- end
22
-
23
- def new
24
- controller.render 'active_element/default_views/new', locals: { record: model.new, namespace: namespace }
25
- end
26
-
27
- def create # rubocop:disable Metrics/AbcSize
28
- new_record = model.new(default_record_params.params)
29
- # Ensure associations are applied:
30
- if new_record.save && new_record.reload.update(default_record_params.params)
31
- controller.flash.notice = "#{new_record.model_name.to_s.titleize} created successfully."
32
- controller.redirect_to record_path(new_record, :show).path
33
- else
34
- controller.flash.now.alert = "Failed to create #{model.name.to_s.titleize}."
35
- controller.render 'active_element/default_views/new', locals: { record: new_record, namespace: namespace }
36
- end
37
- end
38
-
39
- def edit
40
- controller.render 'active_element/default_views/edit', locals: { record: record, namespace: namespace }
41
- end
42
-
43
- def update # rubocop:disable Metrics/AbcSize
44
- if record.update(default_record_params.params)
45
- controller.flash.notice = "#{record.model_name.to_s.titleize} updated successfully."
46
- controller.redirect_to record_path(record, :show).path
47
- else
48
- controller.flash.now.alert = "Failed to update #{model.name.to_s.titleize}."
49
- controller.render 'active_element/default_views/edit', locals: { record: record, namespace: namespace }
50
- end
51
- end
52
-
53
- def destroy
54
- record.destroy
55
- controller.flash.notice = "Deleted #{record.model_name.to_s.titleize}."
56
- controller.redirect_to record_path(model, :index).path
57
- end
58
-
59
- private
3
+ require_relative 'default_controller/controller'
4
+ require_relative 'default_controller/actions'
5
+ require_relative 'default_controller/params'
6
+ require_relative 'default_controller/json_params'
7
+ require_relative 'default_controller/search'
60
8
 
61
- attr_reader :controller
62
-
63
- def default_record_params
64
- @default_record_params ||= ActiveElement::DefaultRecordParams.new(controller: controller, model: model)
65
- end
66
-
67
- def default_text_search
68
- @default_text_search ||= ActiveElement::DefaultTextSearch.new(controller: controller, model: model)
69
- end
70
-
71
- def record_path(record, type = nil)
72
- ActiveElement::Components::Util::RecordPath.new(record: record, controller: controller, type: type)
73
- end
74
-
75
- def namespace
76
- controller.controller_path.rpartition('/').first.presence&.to_sym
77
- end
78
-
79
- def model
80
- controller.controller_name.classify.constantize
81
- end
82
-
83
- def record
84
- @record ||= model.find(controller.params[:id])
85
- end
86
-
87
- def collection
88
- return model.all unless default_text_search.text_search?
89
-
90
- model.left_outer_joins(default_text_search.search_relations).where(*default_text_search.text_search)
91
- end
9
+ module ActiveElement
10
+ # Provides default boilerplate functionality for quick setup of an application.
11
+ # Implements all standard Rails controller actions, provides parameter permitting of configured
12
+ # fields and text search functionality.
13
+ module DefaultController
92
14
  end
93
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveElement
4
- VERSION = '0.0.11'
4
+ VERSION = '0.0.13'
5
5
  end