ez-resources 0.1.0

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.
@@ -0,0 +1,20 @@
1
+ = div_for 'collection-search-container'
2
+ = div_for 'collection-search-panel'
3
+ h4 = t('phrases.search_and_filter')
4
+
5
+ = div_for 'collection-search-panel-form'
6
+ = simple_form_for model.search, url: model.path_for(action: :index), method: :get do |f|
7
+ - model.collection_columns.each do |field|
8
+ - if field.searchable && SEARCHABLE_FIELDS.include?(field.type)
9
+ = div_for 'collection-search-panel-field'
10
+ = f.input field_name(field),
11
+ label: field_label(field),
12
+ required: false,
13
+ as: cast_field_type(field.type),
14
+ collection: field.collection,
15
+ include_blank: true
16
+
17
+ = f.submit t('actions.apply'), class: css_for('collection-search-panel-submit-button')
18
+
19
+ - if model.params[:q]
20
+ = link_to t('actions.reset'), model.path_for(action: :index), class: css_for('collection-search-panel-reset-link')
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ez
4
+ module Resources
5
+ class SearchCell < ApplicationCell
6
+ SEARCHABLE_FIELDS = %i[string association select link boolean].freeze
7
+
8
+ form
9
+
10
+ def cast_field_type(type)
11
+ case type
12
+ when :link then :string
13
+ when :boolean then :select
14
+ when :association then :string
15
+ else
16
+ type
17
+ end
18
+ end
19
+
20
+ def field_name(field)
21
+ if association?(field)
22
+ search_key(field.name, field.search_suffix, field.options.fetch(:association))
23
+ else
24
+ search_key(field.name, field.search_suffix)
25
+ end
26
+ end
27
+
28
+ def field_label(field)
29
+ "#{field.title} (#{t "search.suffix.#{field.search_suffix}"})"
30
+ end
31
+
32
+ private
33
+
34
+ def search_key(name, search_suffix, association_prefix = nil)
35
+ [association_prefix, name, search_suffix].compact.join('_').to_sym
36
+ end
37
+
38
+ def association?(field)
39
+ field.type == :association && field.options[:association].present?
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ez/resources/engine'
4
+ require 'ez/resources/manager'
5
+
6
+ require 'ez/configurator'
7
+ module Ez
8
+ module Resources
9
+ include Ez::Configurator
10
+
11
+ configure do |config|
12
+ config.ignore_fields = %w[id created_at updated_at]
13
+ config.i18n_scope = 'ez_resources'
14
+ end
15
+
16
+ BaseError = Class.new(StandardError)
17
+ GuessingError = Class.new(BaseError)
18
+ ConfigurationError = Class.new(BaseError)
19
+ UnavailableError = Class.new(BaseError)
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cells-rails'
4
+ require 'cells-slim'
5
+ require 'simple_form'
6
+ require 'pagy'
7
+ require 'ransack'
8
+
9
+ module Ez
10
+ module Resources
11
+ class Engine < ::Rails::Engine
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ez/resources/manager/dsl'
4
+ require 'ez/resources/manager/config'
5
+
6
+ module Ez
7
+ module Resources
8
+ module Manager
9
+ include ::Cell::RailsExtensions::ActionController
10
+
11
+ def self.included(base)
12
+ base.extend(DSL)
13
+
14
+ base.rescue_from UnavailableError do
15
+ if Ez::Resources.config.ui_failed_hook
16
+ instance_exec(&Ez::Resources.config.ui_failed_hook)
17
+ else
18
+ flash[:alert] = t('messages.unavailable', scope: Ez::Resources.config.i18n_scope)
19
+ redirect_to '/'
20
+ end
21
+ end
22
+ end
23
+
24
+ def index
25
+ Manager::Hooks.can!(:can_list?, ez_resource_config)
26
+
27
+ ez_resource_view :collection, ez_resource_config
28
+ end
29
+
30
+ def show
31
+ Manager::Hooks.can!(:can_read?, ez_resource_config)
32
+
33
+ if ez_resource_config.show_action_renders_form?
34
+ ez_resource_view :form, ez_resource_config
35
+ else
36
+ # TODO: render show cell
37
+ end
38
+ end
39
+
40
+ def new
41
+ Manager::Hooks.can!(:can_create?, ez_resource_config)
42
+
43
+ ez_resource_view :form, ez_resource_config
44
+ end
45
+
46
+ def create
47
+ Manager::Hooks.can!(:can_create?, ez_resource_config)
48
+
49
+ @ez_resource = ez_resource_config.model.new(ez_resource_params)
50
+
51
+ if @ez_resource.save
52
+ flash[:notice] = t('messages.created',
53
+ resource_name: ez_resource_config.resource_name,
54
+ scope: Ez::Resources.config.i18n_scope)
55
+
56
+ redirect_to ez_resource_config.path_for(action: :index)
57
+ else
58
+ flash[:alert] = t('messages.invalid',
59
+ resource_name: ez_resource_config.resource_name,
60
+ scope: Ez::Resources.config.i18n_scope)
61
+
62
+ ez_resource_view :form, ez_resource_config
63
+ end
64
+ end
65
+
66
+ def edit
67
+ Manager::Hooks.can!(:can_update?, ez_resource_config, ez_resource_config.data)
68
+
69
+ ez_resource_view :form, ez_resource_config
70
+ end
71
+
72
+ def update
73
+ Manager::Hooks.can!(:can_update?, ez_resource_config, ez_resource_config.data)
74
+
75
+ @ez_resource = ez_resource_config.data
76
+
77
+ if @ez_resource.update(ez_resource_params)
78
+ flash[:notice] = t('messages.updated',
79
+ resource_name: ez_resource_config.resource_name,
80
+ scope: Ez::Resources.config.i18n_scope)
81
+
82
+ redirect_to ez_resource_config.path_for(action: :index)
83
+ else
84
+ flash[:alert] = t('messages.invalid',
85
+ resource_name: ez_resource_config.resource_name,
86
+ scope: Ez::Resources.config.i18n_scope)
87
+
88
+ ez_resource_view :form, ez_resource_config
89
+ end
90
+ end
91
+
92
+ # TODO: Later
93
+ def destroy; end
94
+
95
+ private
96
+
97
+ def ez_resource_view(cell_name, *args)
98
+ render html: cell("ez/resources/#{cell_name}", *args), layout: true
99
+ end
100
+
101
+ def ez_resource_params
102
+ params.require(:ez_resource).permit(ez_resource_config.form_fields.map(&:name))
103
+ end
104
+
105
+ def ez_resource_config
106
+ Config.new(
107
+ controller: self,
108
+ dsl_config: self.class.ez_resource_config_store,
109
+ data: @ez_resource
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ez
4
+ module Resources
5
+ module Manager
6
+ class Action
7
+ attr_reader :name, :builder, :options
8
+
9
+ def initialize(name, builder, options = {})
10
+ @name = name
11
+ @builder = builder
12
+ @options = options
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ez
4
+ module Resources
5
+ module Manager
6
+ class Config
7
+ include Pagy::Backend
8
+
9
+ DEFAULT_ACTIONS = %i[index show new create edit update destroy].freeze
10
+
11
+ attr_reader :paginator, :search, :controller
12
+
13
+ def initialize(controller:, dsl_config:, data: nil)
14
+ @controller = controller
15
+ @dsl_config = dsl_config
16
+ @data = data
17
+ end
18
+
19
+ def data
20
+ @data ||= case controller.action_name
21
+ when 'index' then collection
22
+ when 'new' then new_resource
23
+ when 'show' then resource
24
+ when 'edit' then resource
25
+ when 'update' then resource
26
+ else
27
+ raise ConfigurationError, "Invalid action #{controller.action_name}"
28
+ end
29
+ end
30
+
31
+ def total_count
32
+ @total_count ||= model.count
33
+ end
34
+
35
+ def model
36
+ @model ||= dsl_config.model || controller_name.classify.constantize
37
+ rescue NameError
38
+ raise GuessingError, "Ez::Resources tried to guess model name as #{controller_name.classify} but constant is missing. You can define model class explicitly with :model options"
39
+ end
40
+
41
+ def actions
42
+ @actions ||= dsl_config.actions || DEFAULT_ACTIONS
43
+ end
44
+
45
+ def show_action_renders_form?
46
+ @show_action_renders_form ||= dsl_config.show_action_renders_form
47
+ end
48
+
49
+ def hooks
50
+ @hooks ||= dsl_config.hooks || []
51
+ end
52
+
53
+ def resource_label
54
+ @resource_label ||= dsl_config.resource_label || :id
55
+ end
56
+
57
+ def resource_name
58
+ @resource_name ||= controller_name.classify
59
+ end
60
+
61
+ def resources_name
62
+ @resources_name ||= dsl_config.resources_name || resource_name.pluralize
63
+ end
64
+
65
+ def paginate_collection?
66
+ @paginate_collection ||= dsl_config.paginate_collection != false
67
+ end
68
+
69
+ def collection_search?
70
+ @collection_search ||= dsl_config.collection_search != false
71
+ end
72
+
73
+ def collection_views
74
+ @collection_views ||= dsl_config.collection_views || []
75
+ end
76
+
77
+ def collection_columns
78
+ @collection_columns ||= dsl_config.collection_columns || model.columns.map do |column|
79
+ Ez::Resources::Manager::Field.new(
80
+ name: column.name,
81
+ title: column.name.to_s.humanize,
82
+ type: column.sql_type_metadata.type
83
+ )
84
+ end.reject { |col| Ez::Resources.config.ignore_fields.include?(col.name) }
85
+ end
86
+
87
+ def collection_actions
88
+ @colleciton_actions ||= dsl_config.collection_actions || []
89
+ end
90
+
91
+ def form_fields
92
+ @form_fields ||= dsl_config.form_fields || collection_columns || []
93
+ end
94
+
95
+ def path_for(action:, id: nil, params: nil)
96
+ if id
97
+ controller.url_for(action: action, id: id, only_path: true)
98
+ elsif params
99
+ controller.url_for(action: action, **params, only_path: true)
100
+ else
101
+ controller.url_for(action: action, only_path: true)
102
+ end
103
+ end
104
+
105
+ def params
106
+ @params ||= controller.params
107
+ end
108
+
109
+ private
110
+
111
+ attr_reader :dsl_config
112
+
113
+ def collection
114
+ return paginated_collection if paginate_collection?
115
+
116
+ @collection ||= if dsl_config.collection_query
117
+ dsl_config.collection_query.call(model, controller)
118
+ else
119
+ model.all
120
+ end
121
+ end
122
+
123
+ def paginated_collection
124
+ @search = model.ransack(params[:q])
125
+
126
+ @paginated_collection ||= if dsl_config.collection_query
127
+ pagy, paginated_collection = pagy dsl_config.collection_query.call(search.result, controller)
128
+ else
129
+ pagy, paginated_collection = pagy search.result.includes(dsl_config.includes)
130
+ end
131
+
132
+ @paginator = pagy
133
+ paginated_collection
134
+ end
135
+
136
+ def new_resource
137
+ @new_resource ||= model.new
138
+ end
139
+
140
+ def resource
141
+ @resource ||= model.find(controller.params[:id])
142
+ end
143
+
144
+ def controller_name
145
+ @controller_name ||= controller.controller_name
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ez/resources/manager/fields'
4
+ require 'ez/resources/manager/hooks'
5
+
6
+ module Ez
7
+ module Resources
8
+ module Manager
9
+ class ConfigStore
10
+ attr_accessor :actions, :model, :paginate_collection, :resource_name, :resource_label, :resources_name,
11
+ :collection_columns, :collection_query, :collection_search, :show_action_renders_form, :includes,
12
+ :collection_views
13
+
14
+ def collection_columns(&block)
15
+ if block_given?
16
+ @collection_columns = Fields.new(&block).fields
17
+ else
18
+ @collection_columns
19
+ end
20
+ end
21
+
22
+ def collection_actions(&block)
23
+ if block_given?
24
+ @collection_actions = Fields.new(&block).actions
25
+ else
26
+ @collection_actions
27
+ end
28
+ end
29
+
30
+ def form_fields(&block)
31
+ if block_given?
32
+ @form_fields = Fields.new(&block).fields
33
+ else
34
+ @form_fields
35
+ end
36
+ end
37
+
38
+ def hooks(&block)
39
+ if block_given?
40
+ @hooks = Hooks.new(&block).hooks
41
+ else
42
+ @hooks
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ez/resources/manager/config_store'
4
+
5
+ module Ez
6
+ module Resources
7
+ module Manager
8
+ module DSL
9
+ def ez_resource_config_store
10
+ @ez_resource_config_store || Ez::Resources::Manager::ConfigStore.new
11
+ end
12
+
13
+ def ez_resource(&block)
14
+ config = Ez::Resources::Manager::ConfigStore.new
15
+ block.call(config)
16
+
17
+ @ez_resource_config_store = config
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end