active_element 0.0.11 → 0.0.13

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