administrate 0.14.0 → 0.15.0

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