adminable 0.0.1 → 0.0.2
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.
- 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
|