adminable 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +94 -33
- data/app/assets/javascripts/adminable/scripts.js +8 -5
- data/app/assets/stylesheets/adminable/application.scss +15 -0
- data/app/controllers/adminable/application_controller.rb +0 -3
- data/app/controllers/adminable/resources_controller.rb +49 -24
- data/app/models/concerns/adminable/resource_concern.rb +15 -0
- data/app/views/adminable/resources/_form.html.haml +5 -9
- data/app/views/adminable/resources/_search.html.haml +2 -2
- data/app/views/adminable/resources/edit.html.haml +1 -1
- data/app/views/adminable/resources/form/_belongs_to.html.haml +13 -3
- data/app/views/adminable/resources/form/_boolean.html.haml +5 -2
- data/app/views/adminable/resources/form/_date.html.haml +3 -0
- data/app/views/adminable/resources/form/_datetime.html.haml +3 -2
- data/app/views/adminable/resources/form/_decimal.html.haml +3 -2
- data/app/views/adminable/resources/form/_float.html.haml +3 -2
- data/app/views/adminable/resources/form/_has_many.html.haml +12 -8
- data/app/views/adminable/resources/form/_integer.html.haml +3 -2
- data/app/views/adminable/resources/form/_string.html.haml +3 -2
- data/app/views/adminable/resources/form/_text.html.haml +4 -2
- data/app/views/adminable/resources/form/_time.html.haml +3 -0
- data/app/views/adminable/resources/form/_timestamp.html.haml +3 -0
- data/app/views/adminable/resources/index.html.haml +12 -14
- data/app/views/adminable/resources/index/_belongs_to.html.haml +4 -5
- data/app/views/adminable/resources/index/_date.html.haml +2 -0
- data/app/views/adminable/resources/index/_has_many.html.haml +1 -2
- data/app/views/adminable/resources/index/_text.html.haml +1 -1
- data/app/views/adminable/resources/index/_time.html.haml +2 -0
- data/app/views/adminable/resources/index/_timestamp.html.haml +2 -0
- data/app/views/adminable/shared/_label.html.haml +6 -3
- data/app/views/layouts/adminable/application.html.haml +3 -4
- data/config/locales/adminable.en.yml +4 -1
- data/config/locales/adminable.ru.yml +21 -0
- data/config/routes.rb +1 -1
- data/lib/adminable.rb +9 -4
- data/lib/adminable/attributes/association.rb +7 -14
- data/lib/adminable/attributes/base.rb +36 -18
- data/lib/adminable/attributes/collection.rb +75 -36
- data/lib/adminable/attributes/types/belongs_to.rb +1 -3
- data/lib/adminable/attributes/types/date.rb +8 -0
- data/lib/adminable/attributes/types/has_many.rb +0 -6
- data/lib/adminable/attributes/types/time.rb +8 -0
- data/lib/adminable/attributes/types/timestamp.rb +8 -0
- data/lib/adminable/configuration.rb +7 -2
- data/lib/adminable/engine.rb +5 -1
- data/lib/adminable/errors.rb +7 -0
- data/lib/adminable/presenters/base_presenter.rb +11 -0
- data/lib/adminable/presenters/entries_presenter.rb +55 -0
- data/lib/adminable/presenters/entry_presenter.rb +68 -0
- data/lib/adminable/resource.rb +9 -15
- data/lib/adminable/version.rb +1 -1
- data/lib/generators/adminable/install/templates/application_controller.rb +2 -0
- data/lib/generators/adminable/install_generator.rb +27 -0
- data/lib/generators/{cms → adminable}/resource/templates/resource_controller.rb.erb +0 -0
- data/lib/generators/{cms → adminable}/resource_generator.rb +0 -0
- metadata +77 -33
- data/lib/adminable/extensions/devise.rb +0 -24
@@ -1,2 +1,3 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
2
|
-
=
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
|
+
= number_field_tag "#{@resource.model.model_name.param_key}[#{attribute.name}]",
|
3
|
+
entry[attribute.name], class: 'form-control'
|
@@ -1,2 +1,3 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
2
|
-
=
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
|
+
= number_field_tag "#{@resource.model.model_name.param_key}[#{attribute.name}]",
|
3
|
+
entry[attribute.name], class: 'form-control'
|
@@ -1,9 +1,13 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
2
|
= hidden_field_tag "#{@resource.model.model_name.param_key}[#{attribute.key}][]", nil
|
3
|
-
#clusterizeScrollArea.clusterize-scroll
|
4
|
-
#clusterizeContentArea.clusterize-content
|
5
|
-
- attribute.
|
6
|
-
.
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
#clusterizeScrollArea.clusterize-scroll.associations
|
4
|
+
#clusterizeContentArea.clusterize-content
|
5
|
+
- attribute.association.all.each do |association_entry|
|
6
|
+
.association
|
7
|
+
%label.c-input.c-checkbox.m-a-0
|
8
|
+
= check_box_tag "#{@resource.model.model_name.param_key}[#{attribute.key}][]",
|
9
|
+
association_entry.id, entry.send(attribute.key).include?(association_entry.id)
|
10
|
+
%span.c-indicator
|
11
|
+
= link_to association_entry.to_name,
|
12
|
+
edit_polymorphic_path(association_entry),
|
13
|
+
target: '_blank'
|
@@ -1,2 +1,3 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
2
|
-
=
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
|
+
= number_field_tag "#{@resource.model.model_name.param_key}[#{attribute.name}]",
|
3
|
+
entry[attribute.name], class: 'form-control'
|
@@ -1,2 +1,3 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
2
|
-
=
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
|
+
= text_field_tag "#{@resource.model.model_name.param_key}[#{attribute.name}]",
|
3
|
+
entry[attribute.name], class: 'form-control'
|
@@ -1,2 +1,4 @@
|
|
1
|
-
= render 'adminable/shared/label',
|
2
|
-
=
|
1
|
+
= render 'adminable/shared/label', attribute: attribute
|
2
|
+
= text_area_tag "#{@resource.model.model_name.param_key}[#{attribute.name}]",
|
3
|
+
entry[attribute.name],
|
4
|
+
class: "form-control #{ 'wysiwyg' if attribute.options[:wysiwyg] }", rows: 5
|
@@ -1,35 +1,33 @@
|
|
1
1
|
.m-b-1.clearfix
|
2
2
|
%h1.pull-md-left.m-b-2
|
3
|
-
= @resource.model.model_name.human
|
3
|
+
= @resource.model.model_name.human(count: 2)
|
4
4
|
= link_to t('adminable.buttons.new', resource: @resource.model.model_name.human),
|
5
5
|
new_polymorphic_path(@resource.model), class: 'btn btn-primary pull-md-right m-b-2'
|
6
6
|
.row
|
7
|
-
.col-md-9
|
7
|
+
%div{ :class => (@resource.attributes.search.any? ? 'col-md-9' : 'col-md-12') }
|
8
8
|
.table-responsive.m-b-3
|
9
9
|
%table.table.table-striped
|
10
10
|
%thead
|
11
11
|
%tr
|
12
|
-
- @resource.attributes.index.
|
13
|
-
%th.text-nowrap{ :class => ('text-md-center' if attribute.center
|
14
|
-
= @
|
12
|
+
- @resource.attributes.index.each do |attribute|
|
13
|
+
%th.text-nowrap{ :class => ('text-md-center' if attribute.options[:center]) }
|
14
|
+
= sort_link(@q, attribute.name, hide_indicator: true)
|
15
15
|
%th
|
16
16
|
%tbody
|
17
17
|
- @entries.each do |entry|
|
18
18
|
%tr
|
19
|
-
- @resource.attributes.index.
|
20
|
-
%td{ :class => ('text-md-center' if attribute.center
|
19
|
+
- @resource.attributes.index.each do |attribute|
|
20
|
+
%td{ :class => ('text-md-center' if attribute.options[:center]) }
|
21
21
|
= render attribute.index_partial_path, entry: entry,
|
22
22
|
attribute: attribute, value: entry[attribute.key]
|
23
23
|
%td.text-nowrap.text-md-right
|
24
|
-
=
|
25
|
-
|
26
|
-
= link_to t('adminable.buttons.delete'), polymorphic_path(entry),
|
27
|
-
class: 'label label-danger', method: :delete,
|
28
|
-
data: { confirm: t('adminable.ui.confirm') }
|
24
|
+
= entry.link_to_edit_small
|
25
|
+
= entry.link_to_delete_small
|
29
26
|
- if @entries.empty?
|
30
27
|
%tr
|
31
28
|
%td.text-md-center.text-muted{ :colspan => @resource.attributes.index.size + 1 }
|
32
29
|
No data available
|
33
|
-
|
34
|
-
|
30
|
+
- if @resource.attributes.search.any?
|
31
|
+
.col-md-3.hidden-sm-down
|
32
|
+
= render 'search'
|
35
33
|
= paginate @entries, views_prefix: 'adminable'
|
@@ -1,5 +1,4 @@
|
|
1
|
-
- if
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
= "##{ value }"
|
1
|
+
- if entry.public_send(attribute.name)
|
2
|
+
= Adminable::EntryPresenter.new(entry.public_send(attribute.name)).link_to_self
|
3
|
+
- else
|
4
|
+
= "##{value}"
|
@@ -1 +1 @@
|
|
1
|
-
= strip_tags(value.truncate(
|
1
|
+
= strip_tags(value).truncate(30).squish
|
@@ -1,5 +1,8 @@
|
|
1
1
|
= label_tag attribute.name do
|
2
2
|
= @resource.model.human_attribute_name(attribute.name)
|
3
|
-
- if attribute.required
|
4
|
-
%
|
5
|
-
|
3
|
+
- if attribute.options[:required]
|
4
|
+
%span.text-muted.m-l-1
|
5
|
+
%i.fa.fa-exclamation-circle{ 'data-toggle' => :tooltip, 'data-placement' => :right, :title => 'Required' }
|
6
|
+
- if %i(belongs_to has_many).include?(attribute.type)
|
7
|
+
%span.text-muted.m-l-1.uncheck-associations
|
8
|
+
%i.fa.fa-remove{ 'data-toggle' => :tooltip, 'data-placement' => :right, :title => 'Uncheck all' }
|
@@ -14,7 +14,7 @@
|
|
14
14
|
%body
|
15
15
|
#collapsing-nav.collapse.p-y-1
|
16
16
|
- Adminable::Configuration.resources.each do |resource|
|
17
|
-
= link_to resource.model.model_name.human
|
17
|
+
= link_to resource.model.model_name.human(count: 2),
|
18
18
|
polymorphic_path(resource.route), class: 'btn btn-link btn-block'
|
19
19
|
.navbar.navbar-full.navbar-dark.bg-primary.m-b-3
|
20
20
|
.hidden-md-up
|
@@ -27,14 +27,13 @@
|
|
27
27
|
%i.fa.fa-home
|
28
28
|
- Adminable::Configuration.resources.each do |resource|
|
29
29
|
%li.nav-item{ :class => ('active' if current_page?(File.join('/adminable/', resource.name))) }
|
30
|
-
= link_to resource.model.model_name.human
|
30
|
+
= link_to resource.model.model_name.human(count: 2),
|
31
31
|
polymorphic_path(resource.route), class: 'nav-link'
|
32
32
|
.container.p-b-3
|
33
33
|
- if flash[:alert] || notice.present?
|
34
34
|
.p-b-2
|
35
35
|
- if flash[:alert]
|
36
|
-
|
37
|
-
= render 'adminable/shared/alert', message: alert, type: 'danger'
|
36
|
+
= render 'adminable/shared/alert', message: alert, type: 'danger'
|
38
37
|
- if notice.present?
|
39
38
|
= render 'adminable/shared/alert', message: notice, type: 'success'
|
40
39
|
= yield
|
@@ -4,13 +4,16 @@ en:
|
|
4
4
|
ui:
|
5
5
|
confirm: Are you sure?
|
6
6
|
not_selected: Not selected
|
7
|
+
and_more: and %{size} more.
|
7
8
|
buttons:
|
8
9
|
submit: Submit
|
9
10
|
back: Back
|
10
11
|
edit: Edit
|
12
|
+
delete: Delete
|
11
13
|
new: New %{resource}
|
14
|
+
search: Search
|
12
15
|
titles:
|
13
|
-
edit: Edit %{resource}
|
16
|
+
edit: "Edit %{resource} #%{id}"
|
14
17
|
new: New %{resource}
|
15
18
|
resources:
|
16
19
|
created: "%{resource} has been successfully created."
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
ru:
|
3
|
+
adminable:
|
4
|
+
ui:
|
5
|
+
confirm: Вы уверены?
|
6
|
+
not_selected: Не выбрано
|
7
|
+
and_more: и еще %{size}.
|
8
|
+
buttons:
|
9
|
+
submit: Сохранить
|
10
|
+
back: Назад
|
11
|
+
edit: Изменить
|
12
|
+
delete: Удалить
|
13
|
+
new: Новый %{resource}
|
14
|
+
search: Поиск
|
15
|
+
titles:
|
16
|
+
edit: "Редактирование %{resource} #%{id}"
|
17
|
+
new: Создание %{resource}
|
18
|
+
resources:
|
19
|
+
created: "%{resource} успешно создан."
|
20
|
+
updated: "%{resource} успешно обновлен."
|
21
|
+
deleted: "%{resource} успешно удален."
|
data/config/routes.rb
CHANGED
data/lib/adminable.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'adminable/engine'
|
2
2
|
require 'adminable/configuration'
|
3
|
-
|
4
|
-
require 'adminable/extensions/devise'
|
3
|
+
require 'adminable/errors'
|
5
4
|
|
6
5
|
require 'adminable/resource'
|
7
6
|
|
7
|
+
require 'adminable/presenters/base_presenter'
|
8
|
+
require 'adminable/presenters/entry_presenter'
|
9
|
+
require 'adminable/presenters/entries_presenter'
|
10
|
+
|
8
11
|
require 'adminable/attributes/collection'
|
9
12
|
require 'adminable/attributes/association'
|
10
13
|
|
@@ -12,6 +15,7 @@ require 'adminable/attributes/base'
|
|
12
15
|
|
13
16
|
require 'adminable/attributes/types/belongs_to'
|
14
17
|
require 'adminable/attributes/types/boolean'
|
18
|
+
require 'adminable/attributes/types/date'
|
15
19
|
require 'adminable/attributes/types/datetime'
|
16
20
|
require 'adminable/attributes/types/decimal'
|
17
21
|
require 'adminable/attributes/types/float'
|
@@ -19,15 +23,16 @@ require 'adminable/attributes/types/has_many'
|
|
19
23
|
require 'adminable/attributes/types/integer'
|
20
24
|
require 'adminable/attributes/types/string'
|
21
25
|
require 'adminable/attributes/types/text'
|
26
|
+
require 'adminable/attributes/types/time'
|
27
|
+
require 'adminable/attributes/types/timestamp'
|
22
28
|
|
23
29
|
require 'haml-rails'
|
24
30
|
require 'sass-rails'
|
25
|
-
require 'coffee-rails'
|
26
31
|
require 'jquery-rails'
|
27
32
|
require 'bootstrap'
|
28
33
|
require 'rails-assets-tether'
|
29
|
-
require 'babosa'
|
30
34
|
require 'kaminari'
|
35
|
+
require 'ransack'
|
31
36
|
|
32
37
|
module Adminable
|
33
38
|
end
|
@@ -1,21 +1,14 @@
|
|
1
1
|
module Adminable
|
2
2
|
module Attributes
|
3
|
-
|
4
|
-
attr_reader :
|
3
|
+
class Association
|
4
|
+
attr_reader :reflection, :model, :all
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# @param reflection [Object] ActiveRecord::Reflection::HasManyReflection
|
7
|
+
def initialize(reflection)
|
8
|
+
@reflection = reflection
|
9
|
+
@model = @reflection.klass
|
10
|
+
@all = Adminable::EntriesPresenter.new(@model)
|
10
11
|
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def association_option_name(entry)
|
15
|
-
%i(email login name title id).each do |a|
|
16
|
-
return entry.try(a) unless entry.try(a).nil?
|
17
|
-
end
|
18
|
-
end
|
19
12
|
end
|
20
13
|
end
|
21
14
|
end
|
@@ -1,33 +1,38 @@
|
|
1
1
|
module Adminable
|
2
2
|
module Attributes
|
3
|
+
# Base class for attributes types
|
4
|
+
# @note Cannot be initialized
|
3
5
|
class Base
|
4
|
-
|
5
|
-
attr_reader :key, :ransack_name
|
6
|
+
attr_reader :name, :options, :key, :strong_parameter, :association
|
6
7
|
|
8
|
+
# @param name [Symbol] attribute name e.g. `:id` or `:title`
|
9
|
+
# @param options [Hash] options, see {default_options}
|
7
10
|
def initialize(name, options = {})
|
8
11
|
raise 'Base class cannot be initialized' if self.class == Base
|
9
12
|
|
10
|
-
@name = name
|
11
|
-
@
|
12
|
-
@ransack_name = "#{@name}_cont"
|
13
|
-
@association = options[:association]
|
14
|
-
@required = options[:required] || false
|
15
|
-
@show = true
|
16
|
-
@center = %w(integer boolean float decimal).include?(type)
|
17
|
-
@wysiwyg = %w(text).include?(type)
|
18
|
-
end
|
13
|
+
@name = name.to_sym
|
14
|
+
@options = default_options.merge(options)
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
@association = Adminable::Attributes::Association.new(
|
17
|
+
options[:association]
|
18
|
+
) if options[:association]
|
19
|
+
end
|
24
20
|
|
25
|
-
def
|
26
|
-
|
21
|
+
def key
|
22
|
+
@key ||= name
|
27
23
|
end
|
28
24
|
|
29
25
|
def strong_parameter
|
30
|
-
key
|
26
|
+
@strong_parameter ||= key
|
27
|
+
end
|
28
|
+
|
29
|
+
def ransack_name
|
30
|
+
@ransack_name ||= "#{name}_cont"
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Symbol] class type e.g. `:text` for Adminable::Attributes::Text
|
34
|
+
def type
|
35
|
+
@type ||= self.class.name.demodulize.underscore.to_sym
|
31
36
|
end
|
32
37
|
|
33
38
|
def index_partial_path
|
@@ -37,6 +42,19 @@ module Adminable
|
|
37
42
|
def form_partial_path
|
38
43
|
"adminable/resources/form/#{type}"
|
39
44
|
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def default_options
|
49
|
+
{
|
50
|
+
index: %i(belongs_to has_many).exclude?(type),
|
51
|
+
search: false,
|
52
|
+
form: %i(id created_at updated_at).exclude?(name),
|
53
|
+
required: false,
|
54
|
+
center: %i(integer boolean float decimal).include?(type),
|
55
|
+
wysiwyg: false
|
56
|
+
}
|
57
|
+
end
|
40
58
|
end
|
41
59
|
end
|
42
60
|
end
|
@@ -1,73 +1,112 @@
|
|
1
1
|
module Adminable
|
2
2
|
module Attributes
|
3
3
|
class Collection
|
4
|
-
|
5
|
-
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
6
|
|
7
|
+
def_delegators :@all, :[], :<<, :each, :first, :last, :push, :unshift
|
8
|
+
|
9
|
+
attr_reader :model, :all
|
10
|
+
|
11
|
+
# @param model [Class] model from Rails application e.g. `User` or `Post`
|
7
12
|
def initialize(model)
|
8
13
|
@model = model
|
14
|
+
@all ||= columns + associations
|
9
15
|
end
|
10
16
|
|
11
17
|
def index
|
12
|
-
|
18
|
+
all.select { |attribute| attribute.options[:index] }
|
13
19
|
end
|
14
20
|
|
15
21
|
def form
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def ransack
|
20
|
-
@ransack ||= columns.except(:id, :created_at, :updated_at)
|
21
|
-
.select { |k, v| %w(string text).include?(v.type) }
|
22
|
-
end
|
23
|
-
|
24
|
-
def all
|
25
|
-
[columns, belongs_to, has_many].inject(&:merge)
|
22
|
+
all.select { |attribute| attribute.options[:form] }
|
26
23
|
end
|
27
24
|
|
28
|
-
def
|
29
|
-
[
|
25
|
+
def search
|
26
|
+
all.select { |attribute| attribute.options[:search] }
|
30
27
|
end
|
31
28
|
|
29
|
+
# Collects attributes from model columns
|
30
|
+
# @return [Array]
|
31
|
+
#
|
32
|
+
# rubocop:disable Metrics/MethodLength
|
32
33
|
def columns
|
33
|
-
@columns ||=
|
34
|
+
@columns ||= [].tap do |attributes|
|
34
35
|
@model.columns.reject { |a| a.name.match(/_id$/) }.each do |column|
|
35
|
-
|
36
|
-
.
|
36
|
+
begin
|
37
|
+
attributes << resolve(column.type).new(
|
37
38
|
column.name,
|
38
|
-
required:
|
39
|
+
required: required?(column.name)
|
39
40
|
)
|
41
|
+
rescue Adminable::AttributeNotImplemented
|
42
|
+
next
|
43
|
+
end
|
40
44
|
end
|
41
|
-
end
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
# Collects attributes from model associations
|
49
|
+
# @return [Array]
|
50
|
+
def associations
|
51
|
+
@associations ||= [].tap do |attributes|
|
52
|
+
@model.reflect_on_all_associations.each do |association|
|
53
|
+
attributes << resolve(association.macro).new(
|
48
54
|
association.name,
|
49
|
-
required:
|
55
|
+
required: required?(association.name),
|
50
56
|
association: association
|
51
57
|
)
|
52
58
|
end
|
53
|
-
end
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
def configure
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
|
66
|
+
# Changes options for given attribute
|
67
|
+
# @param name [Symbol] name of attribute e.g. `:title`
|
68
|
+
# @param options [Hash] options to update
|
69
|
+
def set(*args)
|
70
|
+
options = args.extract_options!
|
71
|
+
names = args
|
72
|
+
|
73
|
+
options.each do |key, value|
|
74
|
+
names.each do |name|
|
75
|
+
get(name).options[key] = value
|
64
76
|
end
|
65
|
-
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Finds attribute by name
|
81
|
+
# @param name [Symbol] name of attribute e.g. `:title`
|
82
|
+
# @return [Object] e.g. `Adminable::Attribute::Types::String` for `:title`
|
83
|
+
def get(name)
|
84
|
+
@all.find { |attribute| attribute.name == name } ||
|
85
|
+
raise(
|
86
|
+
Adminable::AttributeNotFound,
|
87
|
+
"couldn't find attribute with name `#{name}`"
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Adds new attribute to collection
|
92
|
+
# @param name [Symbol] name of attribute e.g. `:title`
|
93
|
+
# @param type [Symbol] type of attribute e.g. `:string`
|
94
|
+
def add(name, type, options = {})
|
95
|
+
@all << resolve(type).new(name, **options)
|
66
96
|
end
|
67
97
|
|
68
98
|
private
|
69
99
|
|
70
|
-
def
|
100
|
+
def resolve(type)
|
101
|
+
"adminable/attributes/types/#{type}".classify.constantize
|
102
|
+
rescue NameError
|
103
|
+
raise(
|
104
|
+
Adminable::AttributeNotImplemented,
|
105
|
+
"type `#{type}` is not supported yet."
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def required?(name)
|
71
110
|
@model.validators_on(name).any? do |validator|
|
72
111
|
validator.class == ActiveRecord::Validations::PresenceValidator
|
73
112
|
end
|