administrate 0.14.0 → 0.15.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/associative.js +1 -0
  3. data/app/assets/stylesheets/administrate/components/_attributes.scss +3 -2
  4. data/app/assets/stylesheets/administrate/components/_field-unit.scss +4 -0
  5. data/app/assets/stylesheets/administrate/components/_main-content.scss +1 -0
  6. data/app/controllers/administrate/application_controller.rb +11 -11
  7. data/app/helpers/administrate/application_helper.rb +10 -23
  8. data/app/views/administrate/application/_collection.html.erb +1 -1
  9. data/app/views/administrate/application/_form.html.erb +1 -1
  10. data/app/views/administrate/application/index.html.erb +2 -2
  11. data/app/views/administrate/application/show.html.erb +1 -1
  12. data/app/views/fields/belongs_to/_form.html.erb +3 -3
  13. data/app/views/fields/has_one/_index.html.erb +1 -1
  14. data/app/views/fields/has_one/_show.html.erb +4 -4
  15. data/app/views/fields/number/_form.html.erb +1 -1
  16. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  17. data/app/views/fields/select/_form.html.erb +2 -2
  18. data/app/views/fields/time/_index.html.erb +3 -1
  19. data/app/views/fields/time/_show.html.erb +3 -1
  20. data/app/views/layouts/administrate/application.html.erb +1 -0
  21. data/config/locales/administrate.fi.yml +30 -0
  22. data/config/locales/administrate.fr.yml +2 -2
  23. data/config/locales/administrate.nl.yml +4 -4
  24. data/config/locales/administrate.pt-BR.yml +2 -2
  25. data/config/locales/administrate.pt.yml +3 -3
  26. data/config/locales/administrate.tr.yml +30 -0
  27. data/config/unicorn.rb +8 -13
  28. data/docs/adding_controllers_without_related_model.md +18 -0
  29. data/docs/customizing_dashboards.md +32 -16
  30. data/docs/extending_administrate.md +27 -0
  31. data/docs/getting_started.md +27 -5
  32. data/docs/guides.md +5 -0
  33. data/docs/guides/hiding_dashboards_from_sidebar.md +19 -0
  34. data/lib/administrate.rb +19 -0
  35. data/lib/administrate/base_dashboard.rb +5 -2
  36. data/lib/administrate/engine.rb +7 -0
  37. data/lib/administrate/field/associative.rb +48 -4
  38. data/lib/administrate/field/base.rb +26 -0
  39. data/lib/administrate/field/belongs_to.rb +13 -3
  40. data/lib/administrate/field/deferred.rb +7 -3
  41. data/lib/administrate/field/has_many.rb +15 -2
  42. data/lib/administrate/field/has_one.rb +28 -8
  43. data/lib/administrate/field/number.rb +19 -2
  44. data/lib/administrate/field/polymorphic.rb +1 -1
  45. data/lib/administrate/order.rb +3 -1
  46. data/lib/administrate/resource_resolver.rb +1 -1
  47. data/lib/administrate/search.rb +11 -8
  48. data/lib/administrate/version.rb +1 -1
  49. data/lib/administrate/view_generator.rb +7 -1
  50. data/lib/generators/administrate/dashboard/dashboard_generator.rb +3 -10
  51. data/lib/generators/administrate/dashboard/templates/dashboard.rb.erb +3 -3
  52. data/lib/generators/administrate/install/install_generator.rb +37 -1
  53. data/lib/generators/administrate/routes/routes_generator.rb +3 -13
  54. data/lib/generators/administrate/views/views_generator.rb +5 -4
  55. metadata +15 -25
  56. data/docs/contributing.md +0 -1
@@ -48,6 +48,32 @@ module Administrate
48
48
  "/fields/#{self.class.field_type}/#{page}"
49
49
  end
50
50
 
51
+ def required?
52
+ return false unless resource.class.respond_to?(:validators_on)
53
+
54
+ resource.class.validators_on(attribute).any? do |v|
55
+ next false unless v.class == ActiveRecord::Validations::PresenceValidator
56
+
57
+ options = v.options
58
+ next false if options.include?(:if)
59
+ next false if options.include?(:unless)
60
+
61
+ if on_option = options[:on]
62
+ if on_option == :create && !resource.persisted?
63
+ next true
64
+ end
65
+
66
+ if on_option == :update && resource.persisted?
67
+ next true
68
+ end
69
+
70
+ next false
71
+ end
72
+
73
+ true
74
+ end
75
+ end
76
+
51
77
  attr_reader :attribute, :data, :options, :page, :resource
52
78
  end
53
79
  end
@@ -3,8 +3,14 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class BelongsTo < Associative
6
- def self.permitted_attribute(attr, _options = nil)
7
- :"#{attr}_id"
6
+ def self.permitted_attribute(attr, options = {})
7
+ resource_class = options[:resource_class]
8
+ if resource_class
9
+ foreign_key_for(resource_class, attr)
10
+ else
11
+ Administrate.warn_of_missing_resource_class
12
+ :"#{attr}_id"
13
+ end
8
14
  end
9
15
 
10
16
  def permitted_attribute
@@ -12,7 +18,7 @@ module Administrate
12
18
  end
13
19
 
14
20
  def associated_resource_options
15
- [nil] + candidate_resources.map do |resource|
21
+ candidate_resources.map do |resource|
16
22
  [display_candidate_resource(resource), resource.send(primary_key)]
17
23
  end
18
24
  end
@@ -21,6 +27,10 @@ module Administrate
21
27
  data && data.send(primary_key)
22
28
  end
23
29
 
30
+ def include_blank_option
31
+ options.fetch(:include_blank, true)
32
+ end
33
+
24
34
  private
25
35
 
26
36
  def candidate_resources
@@ -44,9 +44,13 @@ module Administrate
44
44
  end
45
45
  end
46
46
 
47
- def permitted_attribute(attr, _options = nil)
48
- options.fetch(:foreign_key,
49
- deferred_class.permitted_attribute(attr, options))
47
+ def permitted_attribute(attr, opts = {})
48
+ if options.key?(:foreign_key)
49
+ Administrate.warn_of_deprecated_option(:foreign_key)
50
+ options.fetch(:foreign_key)
51
+ else
52
+ deferred_class.permitted_attribute(attr, options.merge(opts))
53
+ end
50
54
  end
51
55
 
52
56
  delegate :html_class, to: :deferred_class
@@ -7,7 +7,17 @@ module Administrate
7
7
  class HasMany < Associative
8
8
  DEFAULT_LIMIT = 5
9
9
 
10
- def self.permitted_attribute(attr, _options = nil)
10
+ def self.permitted_attribute(attr, _options = {})
11
+ # This may seem arbitrary, and improvable by using reflection.
12
+ # Worry not: here we do exactly what Rails does. Regardless of the name
13
+ # of the foreign key, has_many associations use the suffix `_ids`
14
+ # for this.
15
+ #
16
+ # Eg: if the associated table and primary key are `countries.code`,
17
+ # you may expect `country_codes` as attribute here, but it will
18
+ # be `country_ids` instead.
19
+ #
20
+ # See https://github.com/rails/rails/blob/b30a23f53b52e59d31358f7b80385ee5c2ba3afe/activerecord/lib/active_record/associations/builder/collection_association.rb#L48
11
21
  { "#{attr.to_s.singularize}_ids".to_sym => [] }
12
22
  end
13
23
 
@@ -36,7 +46,10 @@ module Administrate
36
46
  end
37
47
 
38
48
  def permitted_attribute
39
- self.class.permitted_attribute(attribute)
49
+ self.class.permitted_attribute(
50
+ attribute,
51
+ resource_class: resource.class,
52
+ )
40
53
  end
41
54
 
42
55
  def resources(page = 1, order = self.order)
@@ -3,17 +3,26 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class HasOne < Associative
6
- def self.permitted_attribute(attr, options = nil)
7
- associated_class_name =
8
- if options
9
- options.fetch(:class_name, attr.to_s.singularize.camelcase)
6
+ def self.permitted_attribute(attr, options = {})
7
+ resource_class = options[:resource_class]
8
+ final_associated_class_name =
9
+ if options.key?(:class_name)
10
+ Administrate.warn_of_deprecated_option(:class_name)
11
+ options.fetch(:class_name)
12
+ elsif resource_class
13
+ associated_class_name(resource_class, attr)
10
14
  else
11
- attr
15
+ Administrate.warn_of_missing_resource_class
16
+ if options
17
+ attr.to_s.singularize.camelcase
18
+ else
19
+ attr
20
+ end
12
21
  end
13
22
  related_dashboard_attributes =
14
- Administrate::ResourceResolver.new("admin/#{associated_class_name}").
23
+ Administrate::ResourceResolver.
24
+ new("admin/#{final_associated_class_name}").
15
25
  dashboard_class.new.permitted_attributes + [:id]
16
-
17
26
  { "#{attr}_attributes": related_dashboard_attributes }
18
27
  end
19
28
 
@@ -24,11 +33,22 @@ module Administrate
24
33
  )
25
34
  end
26
35
 
36
+ def nested_show
37
+ @nested_show ||= Administrate::Page::Show.new(
38
+ resolver.dashboard_class.new,
39
+ data || resolver.resource_class.new,
40
+ )
41
+ end
42
+
43
+ def linkable?
44
+ data.try(:persisted?)
45
+ end
46
+
27
47
  private
28
48
 
29
49
  def resolver
30
50
  @resolver ||=
31
- Administrate::ResourceResolver.new("admin/#{associated_class_name}")
51
+ Administrate::ResourceResolver.new("admin/#{associated_class.name}")
32
52
  end
33
53
  end
34
54
  end
@@ -1,16 +1,19 @@
1
1
  require_relative "base"
2
+ require "active_support/number_helper"
2
3
 
3
4
  module Administrate
4
5
  module Field
5
6
  class Number < Field::Base
6
7
  def to_s
7
- data.nil? ? "-" : format_string % value
8
+ result = data.nil? ? "-" : format_string % value
9
+ result = format(result) if options[:format]
10
+ prefix + result + suffix
8
11
  end
9
12
 
10
13
  private
11
14
 
12
15
  def format_string
13
- prefix + "%.#{decimals}f" + suffix
16
+ "%.#{decimals}f"
14
17
  end
15
18
 
16
19
  def prefix
@@ -30,6 +33,20 @@ module Administrate
30
33
  def value
31
34
  data * options.fetch(:multiplier, 1)
32
35
  end
36
+
37
+ def format(result)
38
+ formatter = options[:format][:formatter]
39
+ formatter_options = options[:format][:formatter_options].to_h
40
+
41
+ case formatter
42
+ when :number_to_delimited
43
+ ActiveSupport::NumberHelper.number_to_delimited(
44
+ result, **formatter_options
45
+ )
46
+ else
47
+ result
48
+ end
49
+ end
33
50
  end
34
51
  end
35
52
  end
@@ -3,7 +3,7 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class Polymorphic < BelongsTo
6
- def self.permitted_attribute(attr, _options = nil)
6
+ def self.permitted_attribute(attr, _options = {})
7
7
  { attr => %i{type value} }
8
8
  end
9
9
 
@@ -59,10 +59,12 @@ module Administrate
59
59
  end
60
60
 
61
61
  def order_by_count(relation)
62
+ klass = reflect_association(relation).klass
63
+ query = "COUNT(#{klass.table_name}.#{klass.primary_key}) #{direction}"
62
64
  relation.
63
65
  left_joins(attribute.to_sym).
64
66
  group(:id).
65
- reorder("COUNT(#{attribute}.id) #{direction}")
67
+ reorder(Arel.sql(query))
66
68
  end
67
69
 
68
70
  def order_by_id(relation)
@@ -21,7 +21,7 @@ module Administrate
21
21
  end
22
22
 
23
23
  def resource_title
24
- model_path_parts.join(" ")
24
+ resource_class.model_name.human
25
25
  end
26
26
 
27
27
  private
@@ -82,8 +82,8 @@ module Administrate
82
82
  search_attributes.map do |attr|
83
83
  table_name = query_table_name(attr)
84
84
  searchable_fields(attr).map do |field|
85
- attr_name = column_to_query(field)
86
- "LOWER(CAST(#{table_name}.#{attr_name} AS CHAR(256))) LIKE ?"
85
+ column_name = column_to_query(field)
86
+ "LOWER(CAST(#{table_name}.#{column_name} AS CHAR(256))) LIKE ?"
87
87
  end.join(" OR ")
88
88
  end.join(" OR ")
89
89
  end
@@ -109,7 +109,7 @@ module Administrate
109
109
 
110
110
  def search_results(resources)
111
111
  resources.
112
- joins(tables_to_join).
112
+ left_joins(tables_to_join).
113
113
  where(query_template, *query_values)
114
114
  end
115
115
 
@@ -128,11 +128,14 @@ module Administrate
128
128
  def query_table_name(attr)
129
129
  if association_search?(attr)
130
130
  provided_class_name = attribute_types[attr].options[:class_name]
131
- if provided_class_name
132
- provided_class_name.constantize.table_name
133
- else
134
- ActiveRecord::Base.connection.quote_table_name(attr.to_s.pluralize)
135
- end
131
+ unquoted_table_name =
132
+ if provided_class_name
133
+ Administrate.warn_of_deprecated_option(:class_name)
134
+ provided_class_name.constantize.table_name
135
+ else
136
+ @scoped_resource.reflect_on_association(attr).klass.table_name
137
+ end
138
+ ActiveRecord::Base.connection.quote_table_name(unquoted_table_name)
136
139
  else
137
140
  ActiveRecord::Base.connection.
138
141
  quote_table_name(@scoped_resource.table_name)
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.14.0".freeze
2
+ VERSION = "0.15.0".freeze
3
3
  end
@@ -1,9 +1,11 @@
1
1
  require "rails/generators/base"
2
2
  require "administrate/generator_helpers"
3
+ require "administrate/namespace"
3
4
 
4
5
  module Administrate
5
6
  class ViewGenerator < Rails::Generators::Base
6
7
  include Administrate::GeneratorHelpers
8
+ class_option :namespace, type: :string, default: "admin"
7
9
 
8
10
  def self.template_source_path
9
11
  File.expand_path(
@@ -14,12 +16,16 @@ module Administrate
14
16
 
15
17
  private
16
18
 
19
+ def namespace
20
+ options[:namespace]
21
+ end
22
+
17
23
  def copy_resource_template(template_name)
18
24
  template_file = "#{template_name}.html.erb"
19
25
 
20
26
  copy_file(
21
27
  template_file,
22
- "app/views/admin/#{resource_path}/#{template_file}",
28
+ "app/views/#{namespace}/#{resource_path}/#{template_file}",
23
29
  )
24
30
  end
25
31
 
@@ -13,6 +13,7 @@ module Administrate
13
13
  time: "Field::Time",
14
14
  text: "Field::Text",
15
15
  string: "Field::String",
16
+ uuid: "Field::String",
16
17
  }
17
18
 
18
19
  ATTRIBUTE_OPTIONS_MAPPING = {
@@ -109,11 +110,11 @@ module Administrate
109
110
  if relationship.has_one?
110
111
  "Field::HasOne"
111
112
  elsif relationship.collection?
112
- "Field::HasMany" + relationship_options_string(relationship)
113
+ "Field::HasMany"
113
114
  elsif relationship.polymorphic?
114
115
  "Field::Polymorphic"
115
116
  else
116
- "Field::BelongsTo" + relationship_options_string(relationship)
117
+ "Field::BelongsTo"
117
118
  end
118
119
  end
119
120
 
@@ -121,14 +122,6 @@ module Administrate
121
122
  @klass ||= Object.const_get(class_name)
122
123
  end
123
124
 
124
- def relationship_options_string(relationship)
125
- if relationship.class_name != relationship.name.to_s.classify
126
- options_string(class_name: relationship.class_name)
127
- else
128
- ""
129
- end
130
- end
131
-
132
125
  def options_string(options)
133
126
  if options.any?
134
127
  ".with_options(#{inspect_hash_as_ruby(options)})"
@@ -21,7 +21,7 @@ class <%= class_name %>Dashboard < Administrate::BaseDashboard
21
21
  COLLECTION_ATTRIBUTES = %i[
22
22
  <%=
23
23
  attributes.first(COLLECTION_ATTRIBUTE_LIMIT).map do |attr|
24
- " #{attr}"
24
+ " #{attr}"
25
25
  end.join("\n")
26
26
  %>
27
27
  ].freeze
@@ -31,7 +31,7 @@ class <%= class_name %>Dashboard < Administrate::BaseDashboard
31
31
  SHOW_PAGE_ATTRIBUTES = %i[
32
32
  <%=
33
33
  attributes.map do |attr|
34
- " #{attr}"
34
+ " #{attr}"
35
35
  end.join("\n")
36
36
  %>
37
37
  ].freeze
@@ -42,7 +42,7 @@ class <%= class_name %>Dashboard < Administrate::BaseDashboard
42
42
  FORM_ATTRIBUTES = %i[
43
43
  <%=
44
44
  form_attributes.map do |attr|
45
- " #{attr}"
45
+ " #{attr}"
46
46
  end.join("\n")
47
47
  %>
48
48
  ].freeze
@@ -1,3 +1,9 @@
1
+ if defined?(Zeitwerk)
2
+ Zeitwerk::Loader.eager_load_all
3
+ else
4
+ Rails.application.eager_load!
5
+ end
6
+
1
7
  require "rails/generators/base"
2
8
  require "administrate/generator_helpers"
3
9
  require "administrate/namespace"
@@ -13,7 +19,7 @@ module Administrate
13
19
  def run_routes_generator
14
20
  if dashboard_resources.none?
15
21
  call_generator("administrate:routes", "--namespace", namespace)
16
- load Rails.root.join("config/routes.rb")
22
+ Rails.application.reload_routes!
17
23
  end
18
24
  end
19
25
 
@@ -31,6 +37,12 @@ module Administrate
31
37
  end
32
38
  end
33
39
 
40
+ def model_check
41
+ if valid_dashboard_models.none?
42
+ puts "WARNING: Add models before installing Administrate."
43
+ end
44
+ end
45
+
34
46
  private
35
47
 
36
48
  def namespace
@@ -44,6 +56,30 @@ module Administrate
44
56
  def dashboard_resources
45
57
  Administrate::Namespace.new(namespace).resources
46
58
  end
59
+
60
+ def valid_dashboard_models
61
+ database_models - invalid_dashboard_models
62
+ end
63
+
64
+ def database_models
65
+ ActiveRecord::Base.descendants.reject(&:abstract_class?)
66
+ end
67
+
68
+ def invalid_dashboard_models
69
+ (models_without_tables + namespaced_models + unnamed_constants).uniq
70
+ end
71
+
72
+ def models_without_tables
73
+ database_models.reject(&:table_exists?)
74
+ end
75
+
76
+ def namespaced_models
77
+ database_models.select { |model| model.to_s.include?("::") }
78
+ end
79
+
80
+ def unnamed_constants
81
+ ActiveRecord::Base.descendants.reject { |d| d.name == d.to_s }
82
+ end
47
83
  end
48
84
  end
49
85
  end