administrate 0.12.0 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -2
  3. data/app/assets/javascripts/administrate/application.js +0 -2
  4. data/app/assets/javascripts/administrate/components/associative.js +5 -0
  5. data/app/assets/javascripts/administrate/components/select.js +3 -0
  6. data/app/assets/javascripts/administrate/components/table.js +1 -1
  7. data/app/assets/stylesheets/administrate/application.scss +0 -1
  8. data/app/assets/stylesheets/administrate/base/_forms.scss +1 -1
  9. data/app/assets/stylesheets/administrate/base/_tables.scss +3 -0
  10. data/app/assets/stylesheets/administrate/components/_attributes.scss +4 -3
  11. data/app/assets/stylesheets/administrate/components/_buttons.scss +20 -0
  12. data/app/assets/stylesheets/administrate/components/_cells.scss +2 -0
  13. data/app/assets/stylesheets/administrate/components/_field-unit.scss +24 -4
  14. data/app/assets/stylesheets/administrate/components/_flashes.scss +2 -10
  15. data/app/assets/stylesheets/administrate/components/_main-content.scss +1 -0
  16. data/app/assets/stylesheets/administrate/components/_navigation.scss +2 -3
  17. data/app/assets/stylesheets/administrate/library/_variables.scss +11 -9
  18. data/app/controllers/administrate/application_controller.rb +127 -25
  19. data/app/controllers/concerns/administrate/punditize.rb +44 -20
  20. data/app/helpers/administrate/application_helper.rb +56 -20
  21. data/app/views/administrate/application/_collection.html.erb +26 -29
  22. data/app/views/administrate/application/_collection_header_actions.html.erb +4 -0
  23. data/app/views/administrate/application/_collection_item_actions.html.erb +17 -0
  24. data/app/views/administrate/application/_flashes.html.erb +1 -0
  25. data/app/views/administrate/application/_form.html.erb +20 -5
  26. data/app/views/administrate/application/_icons.html.erb +1 -1
  27. data/app/views/administrate/application/_index_header.html.erb +28 -0
  28. data/app/views/administrate/application/_navigation.html.erb +6 -4
  29. data/app/views/administrate/application/_pagination.html.erb +1 -0
  30. data/app/views/administrate/application/edit.html.erb +2 -2
  31. data/app/views/administrate/application/index.html.erb +9 -29
  32. data/app/views/administrate/application/new.html.erb +1 -1
  33. data/app/views/administrate/application/show.html.erb +28 -12
  34. data/app/views/fields/belongs_to/_form.html.erb +3 -3
  35. data/app/views/fields/belongs_to/_index.html.erb +1 -1
  36. data/app/views/fields/belongs_to/_show.html.erb +1 -1
  37. data/app/views/fields/date/_form.html.erb +22 -0
  38. data/app/views/fields/date/_index.html.erb +21 -0
  39. data/app/views/fields/date/_show.html.erb +21 -0
  40. data/app/views/fields/date_time/_form.html.erb +1 -3
  41. data/app/views/fields/has_many/_index.html.erb +1 -1
  42. data/app/views/fields/has_many/_show.html.erb +2 -1
  43. data/app/views/fields/has_one/_form.html.erb +15 -5
  44. data/app/views/fields/has_one/_index.html.erb +3 -2
  45. data/app/views/fields/has_one/_show.html.erb +22 -15
  46. data/app/views/fields/number/_form.html.erb +1 -1
  47. data/app/views/fields/polymorphic/_index.html.erb +2 -1
  48. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  49. data/app/views/fields/select/_form.html.erb +9 -8
  50. data/app/views/fields/string/_show.html.erb +2 -2
  51. data/app/views/fields/text/_show.html.erb +2 -3
  52. data/app/views/fields/time/_form.html.erb +2 -2
  53. data/app/views/fields/time/_index.html.erb +3 -1
  54. data/app/views/fields/time/_show.html.erb +3 -1
  55. data/app/views/fields/url/_index.html.erb +2 -2
  56. data/app/views/fields/url/_show.html.erb +2 -2
  57. data/app/views/layouts/administrate/application.html.erb +2 -1
  58. data/config/locales/administrate.ar.yml +2 -0
  59. data/config/locales/administrate.bs.yml +2 -0
  60. data/config/locales/administrate.ca.yml +2 -0
  61. data/config/locales/administrate.da.yml +2 -0
  62. data/config/locales/administrate.de.yml +4 -2
  63. data/config/locales/administrate.en.yml +2 -0
  64. data/config/locales/administrate.es.yml +2 -0
  65. data/config/locales/administrate.fi.yml +30 -0
  66. data/config/locales/administrate.fr.yml +4 -2
  67. data/config/locales/administrate.id.yml +2 -0
  68. data/config/locales/administrate.it.yml +2 -0
  69. data/config/locales/administrate.ja.yml +7 -5
  70. data/config/locales/administrate.ko.yml +2 -0
  71. data/config/locales/administrate.nl.yml +7 -5
  72. data/config/locales/administrate.pl.yml +2 -0
  73. data/config/locales/administrate.pt-BR.yml +4 -2
  74. data/config/locales/administrate.pt.yml +4 -2
  75. data/config/locales/administrate.ru.yml +2 -0
  76. data/config/locales/administrate.sl.yml +30 -0
  77. data/config/locales/{administrate.al.yml → administrate.sq.yml} +3 -1
  78. data/config/locales/administrate.sv.yml +2 -0
  79. data/config/locales/administrate.tr.yml +30 -0
  80. data/config/locales/administrate.uk.yml +2 -0
  81. data/config/locales/administrate.vi.yml +2 -0
  82. data/config/locales/administrate.zh-CN.yml +2 -0
  83. data/config/locales/administrate.zh-TW.yml +2 -0
  84. data/docs/adding_controllers_without_related_model.md +52 -0
  85. data/docs/adding_custom_field_types.md +3 -1
  86. data/docs/authentication.md +3 -1
  87. data/docs/authorization.md +47 -22
  88. data/docs/customizing_attribute_partials.md +4 -1
  89. data/docs/customizing_controller_actions.md +67 -1
  90. data/docs/customizing_dashboards.md +171 -40
  91. data/docs/customizing_page_views.md +18 -4
  92. data/docs/extending_administrate.md +27 -0
  93. data/docs/getting_started.md +35 -11
  94. data/docs/guides/customising_search.md +149 -0
  95. data/docs/guides/hiding_dashboards_from_sidebar.md +21 -0
  96. data/docs/guides/scoping_has_many_relations.md +27 -0
  97. data/docs/guides.md +7 -0
  98. data/docs/rails_api.md +5 -3
  99. data/lib/administrate/base_dashboard.rb +73 -14
  100. data/lib/administrate/custom_dashboard.rb +15 -0
  101. data/lib/administrate/engine.rb +9 -2
  102. data/lib/administrate/field/associative.rb +61 -7
  103. data/lib/administrate/field/base.rb +39 -9
  104. data/lib/administrate/field/belongs_to.rb +22 -5
  105. data/lib/administrate/field/date.rb +20 -0
  106. data/lib/administrate/field/deferred.rb +26 -3
  107. data/lib/administrate/field/has_many.rb +35 -8
  108. data/lib/administrate/field/has_one.rb +36 -12
  109. data/lib/administrate/field/number.rb +13 -2
  110. data/lib/administrate/field/polymorphic.rb +7 -6
  111. data/lib/administrate/field/select.rb +23 -4
  112. data/lib/administrate/field/time.rb +11 -0
  113. data/lib/administrate/field/url.rb +4 -0
  114. data/lib/administrate/namespace.rb +5 -1
  115. data/lib/administrate/not_authorized_error.rb +20 -0
  116. data/lib/administrate/order.rb +85 -19
  117. data/lib/administrate/page/base.rb +5 -3
  118. data/lib/administrate/page/collection.rb +1 -0
  119. data/lib/administrate/page/form.rb +12 -4
  120. data/lib/administrate/page/show.rb +10 -2
  121. data/lib/administrate/resource_resolver.rb +4 -3
  122. data/lib/administrate/search.rb +47 -36
  123. data/lib/administrate/version.rb +1 -1
  124. data/lib/administrate/view_generator.rb +14 -3
  125. data/lib/administrate.rb +42 -0
  126. data/lib/generators/administrate/dashboard/dashboard_generator.rb +38 -16
  127. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +23 -11
  128. data/lib/generators/administrate/dashboard/templates/dashboard.rb.erb +4 -4
  129. data/lib/generators/administrate/install/install_generator.rb +43 -2
  130. data/lib/generators/administrate/install/templates/application_controller.rb.erb +4 -4
  131. data/lib/generators/administrate/routes/routes_generator.rb +26 -27
  132. data/lib/generators/administrate/test_record.rb +21 -0
  133. data/lib/generators/administrate/views/views_generator.rb +5 -4
  134. metadata +57 -78
  135. data/app/assets/javascripts/administrate/components/date_time_picker.js +0 -10
  136. data/app/assets/javascripts/administrate/components/has_many_form.js +0 -3
  137. data/config/i18n-tasks.yml +0 -18
  138. data/config/routes.rb +0 -2
  139. data/config/unicorn.rb +0 -30
@@ -3,8 +3,18 @@ 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
14
+ end
15
+
16
+ def self.eager_load?
17
+ true
8
18
  end
9
19
 
10
20
  def permitted_attribute
@@ -12,13 +22,20 @@ module Administrate
12
22
  end
13
23
 
14
24
  def associated_resource_options
15
- [nil] + candidate_resources.map do |resource|
16
- [display_candidate_resource(resource), resource.send(primary_key)]
25
+ candidate_resources.map do |resource|
26
+ [
27
+ display_candidate_resource(resource),
28
+ resource.send(association_primary_key),
29
+ ]
17
30
  end
18
31
  end
19
32
 
20
33
  def selected_option
21
- data && data.send(primary_key)
34
+ data&.send(association_primary_key)
35
+ end
36
+
37
+ def include_blank_option
38
+ options.fetch(:include_blank, true)
22
39
  end
23
40
 
24
41
  private
@@ -0,0 +1,20 @@
1
+ require_relative "base"
2
+
3
+ module Administrate
4
+ module Field
5
+ class Date < Base
6
+ def date
7
+ I18n.localize(
8
+ data.to_date,
9
+ format: format,
10
+ )
11
+ end
12
+
13
+ private
14
+
15
+ def format
16
+ options.fetch(:format, :default)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -21,17 +21,40 @@ module Administrate
21
21
  options == other.options
22
22
  end
23
23
 
24
+ def associative?
25
+ deferred_class.associative?
26
+ end
27
+
28
+ def eager_load?
29
+ deferred_class.eager_load?
30
+ end
31
+
24
32
  def searchable?
25
33
  options.fetch(:searchable, deferred_class.searchable?)
26
34
  end
27
35
 
28
36
  def searchable_field
37
+ Administrate.deprecator.warn(
38
+ "searchable_field is deprecated, use searchable_fields instead",
39
+ )
29
40
  options.fetch(:searchable_field)
30
41
  end
31
42
 
32
- def permitted_attribute(attr, _options = nil)
33
- options.fetch(:foreign_key,
34
- deferred_class.permitted_attribute(attr, options))
43
+ def searchable_fields
44
+ if options.key?(:searchable_field)
45
+ [searchable_field]
46
+ else
47
+ options.fetch(:searchable_fields)
48
+ end
49
+ end
50
+
51
+ def permitted_attribute(attr, opts = {})
52
+ if options.key?(:foreign_key)
53
+ Administrate.warn_of_deprecated_option(:foreign_key)
54
+ options.fetch(:foreign_key)
55
+ else
56
+ deferred_class.permitted_attribute(attr, options.merge(opts))
57
+ end
35
58
  end
36
59
 
37
60
  delegate :html_class, to: :deferred_class
@@ -7,12 +7,26 @@ 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
 
14
24
  def associated_collection(order = self.order)
15
- Administrate::Page::Collection.new(associated_dashboard, order: order)
25
+ Administrate::Page::Collection.new(
26
+ associated_dashboard,
27
+ order: order,
28
+ collection_attributes: options[:collection_attributes],
29
+ )
16
30
  end
17
31
 
18
32
  def attribute_key
@@ -20,32 +34,45 @@ module Administrate
20
34
  end
21
35
 
22
36
  def associated_resource_options
23
- candidate_resources.map do |resource|
24
- [display_candidate_resource(resource), resource.send(primary_key)]
37
+ candidate_resources.map do |associated_resource|
38
+ [
39
+ display_candidate_resource(associated_resource),
40
+ associated_resource.send(association_primary_key),
41
+ ]
25
42
  end
26
43
  end
27
44
 
28
45
  def selected_options
29
46
  return if data.empty?
30
47
 
31
- data.map { |object| object.send(primary_key) }
48
+ data.map { |object| object.send(association_primary_key) }
32
49
  end
33
50
 
34
51
  def limit
35
52
  options.fetch(:limit, DEFAULT_LIMIT)
36
53
  end
37
54
 
55
+ def paginate?
56
+ limit.respond_to?(:positive?) ? limit.positive? : limit.present?
57
+ end
58
+
38
59
  def permitted_attribute
39
- self.class.permitted_attribute(attribute)
60
+ self.class.permitted_attribute(
61
+ attribute,
62
+ resource_class: resource.class,
63
+ )
40
64
  end
41
65
 
42
66
  def resources(page = 1, order = self.order)
43
- resources = order.apply(data).page(page).per(limit)
67
+ resources = order.apply(data)
68
+ if paginate?
69
+ resources = resources.page(page).per(limit)
70
+ end
44
71
  includes.any? ? resources.includes(*includes) : resources
45
72
  end
46
73
 
47
74
  def more_than_limit?
48
- data.count(:all) > limit
75
+ paginate? && data.count(:all) > limit
49
76
  end
50
77
 
51
78
  def data
@@ -3,6 +3,33 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class HasOne < Associative
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)
14
+ else
15
+ Administrate.warn_of_missing_resource_class
16
+ if options
17
+ attr.to_s.singularize.camelcase
18
+ else
19
+ attr
20
+ end
21
+ end
22
+ related_dashboard_attributes =
23
+ Administrate::ResourceResolver.
24
+ new("admin/#{final_associated_class_name}").
25
+ dashboard_class.new.permitted_attributes + [:id]
26
+ { "#{attr}_attributes": related_dashboard_attributes }
27
+ end
28
+
29
+ def self.eager_load?
30
+ true
31
+ end
32
+
6
33
  def nested_form
7
34
  @nested_form ||= Administrate::Page::Form.new(
8
35
  resolver.dashboard_class.new,
@@ -10,25 +37,22 @@ module Administrate
10
37
  )
11
38
  end
12
39
 
13
- def self.permitted_attribute(attr, options = nil)
14
- associated_class_name =
15
- if options
16
- options.fetch(:class_name, attr.to_s.singularize.camelcase)
17
- else
18
- attr
19
- end
20
- related_dashboard_attributes =
21
- Administrate::ResourceResolver.new("admin/#{associated_class_name}").
22
- dashboard_class.new.permitted_attributes + [:id]
40
+ def nested_show
41
+ @nested_show ||= Administrate::Page::Show.new(
42
+ resolver.dashboard_class.new,
43
+ data || resolver.resource_class.new,
44
+ )
45
+ end
23
46
 
24
- { "#{attr}_attributes": related_dashboard_attributes }
47
+ def linkable?
48
+ data.try(:persisted?)
25
49
  end
26
50
 
27
51
  private
28
52
 
29
53
  def resolver
30
54
  @resolver ||=
31
- Administrate::ResourceResolver.new("admin/#{associated_class_name}")
55
+ Administrate::ResourceResolver.new("admin/#{associated_class.name}")
32
56
  end
33
57
  end
34
58
  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,14 @@ 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
+ ActiveSupport::NumberHelper.
42
+ try(formatter, result, **formatter_options) || result
43
+ end
33
44
  end
34
45
  end
35
46
  end
@@ -3,6 +3,10 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class Polymorphic < BelongsTo
6
+ def self.permitted_attribute(attr, _options = {})
7
+ { attr => %i{type value} }
8
+ end
9
+
6
10
  def associated_resource_grouped_options
7
11
  classes.map do |klass|
8
12
  [klass.to_s, candidate_resources_for(klass).map do |resource|
@@ -11,10 +15,6 @@ module Administrate
11
15
  end
12
16
  end
13
17
 
14
- def self.permitted_attribute(attr, _options = nil)
15
- { attr => %i{type value} }
16
- end
17
-
18
18
  def permitted_attribute
19
19
  { attribute => %i{type value} }
20
20
  end
@@ -23,14 +23,15 @@ module Administrate
23
23
  data ? data.to_global_id : nil
24
24
  end
25
25
 
26
- protected
26
+ private
27
27
 
28
28
  def associated_dashboard(klass = data.class)
29
29
  "#{klass.name}Dashboard".constantize.new
30
30
  end
31
31
 
32
32
  def classes
33
- options.fetch(:classes, [])
33
+ klasses = options.fetch(:classes, [])
34
+ klasses.respond_to?(:call) ? klasses.call : klasses
34
35
  end
35
36
 
36
37
  private
@@ -8,13 +8,32 @@ module Administrate
8
8
  end
9
9
 
10
10
  def selectable_options
11
- collection
11
+ values =
12
+ if options.key?(:collection)
13
+ options.fetch(:collection)
14
+ elsif active_record_enum?
15
+ active_record_enum_values
16
+ else
17
+ []
18
+ end
19
+
20
+ if values.respond_to? :call
21
+ values = values.arity.positive? ? values.call(self) : values.call
22
+ end
23
+
24
+ values
12
25
  end
13
26
 
14
- private
27
+ def include_blank_option
28
+ options.fetch(:include_blank, false)
29
+ end
30
+
31
+ def active_record_enum?
32
+ resource.class.defined_enums.key?(attribute.to_s)
33
+ end
15
34
 
16
- def collection
17
- @collection ||= options.fetch(:collection, [])
35
+ def active_record_enum_values
36
+ resource.class.defined_enums[attribute.to_s].map(&:first)
18
37
  end
19
38
  end
20
39
  end
@@ -3,6 +3,17 @@ require_relative "base"
3
3
  module Administrate
4
4
  module Field
5
5
  class Time < Base
6
+ def time
7
+ return I18n.localize(data, format: format) if options[:format]
8
+
9
+ data.strftime("%I:%M%p")
10
+ end
11
+
12
+ private
13
+
14
+ def format
15
+ options[:format]
16
+ end
6
17
  end
7
18
  end
8
19
  end
@@ -11,6 +11,10 @@ module Administrate
11
11
  data.to_s[0...truncation_length]
12
12
  end
13
13
 
14
+ def html_options
15
+ @options[:html_options] || {}
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def truncation_length
@@ -1,7 +1,7 @@
1
1
  module Administrate
2
2
  class Namespace
3
3
  def initialize(namespace)
4
- @namespace = namespace
4
+ @namespace = namespace.to_sym
5
5
  end
6
6
 
7
7
  def resources
@@ -18,6 +18,10 @@ module Administrate
18
18
  end
19
19
  end
20
20
 
21
+ def resources_with_index_route
22
+ routes.select { |_resource, route| route == "index" }.map(&:first).uniq
23
+ end
24
+
21
25
  private
22
26
 
23
27
  attr_reader :namespace
@@ -0,0 +1,20 @@
1
+ module Administrate
2
+ class NotAuthorizedError < StandardError
3
+ def initialize(action:, resource:)
4
+ @action = action
5
+ @resource = resource
6
+
7
+ case resource
8
+ when String, Symbol
9
+ super("Not allowed to perform #{action.inspect} on #{resource.inspect}")
10
+ when Module
11
+ super("Not allowed to perform #{action.inspect} on #{resource.name}")
12
+ else
13
+ super(
14
+ "Not allowed to perform #{action.inspect} on the given " +
15
+ resource.class.name
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,18 +1,19 @@
1
1
  module Administrate
2
2
  class Order
3
- def initialize(attribute = nil, direction = nil)
3
+ def initialize(attribute = nil, direction = nil, association_attribute: nil)
4
4
  @attribute = attribute
5
- @direction = direction || :asc
5
+ @direction = sanitize_direction(direction)
6
+ @association_attribute = association_attribute
6
7
  end
7
8
 
8
9
  def apply(relation)
9
10
  return order_by_association(relation) unless
10
11
  reflect_association(relation).nil?
11
12
 
12
- order = "#{relation.table_name}.#{attribute} #{direction}"
13
+ order = relation.arel_table[attribute].public_send(direction)
13
14
 
14
15
  return relation.reorder(order) if
15
- relation.columns_hash.keys.include?(attribute.to_s)
16
+ column_exist?(relation, attribute)
16
17
 
17
18
  relation
18
19
  end
@@ -32,7 +33,11 @@ module Administrate
32
33
 
33
34
  private
34
35
 
35
- attr_reader :attribute
36
+ attr_reader :attribute, :association_attribute
37
+
38
+ def sanitize_direction(direction)
39
+ %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
40
+ end
36
41
 
37
42
  def reversed_direction_param_for(attr)
38
43
  if ordered_by?(attr)
@@ -43,38 +48,99 @@ module Administrate
43
48
  end
44
49
 
45
50
  def opposite_direction
46
- direction.to_sym == :asc ? :desc : :asc
51
+ direction == :asc ? :desc : :asc
47
52
  end
48
53
 
49
54
  def order_by_association(relation)
50
- return order_by_count(relation) if has_many_attribute?(relation)
51
-
52
- return order_by_id(relation) if belongs_to_attribute?(relation)
53
-
54
- relation
55
+ case relation_type(relation)
56
+ when :has_many
57
+ order_by_count(relation)
58
+ when :belongs_to
59
+ order_by_belongs_to(relation)
60
+ when :has_one
61
+ order_by_has_one(relation)
62
+ else
63
+ relation
64
+ end
55
65
  end
56
66
 
57
67
  def order_by_count(relation)
68
+ klass = reflect_association(relation).klass
69
+ query = klass.arel_table[klass.primary_key].count.public_send(direction)
58
70
  relation.
59
- left_joins(attribute.to_sym).
60
- group(:id).
61
- reorder("COUNT(#{attribute}.id) #{direction}")
71
+ left_joins(attribute.to_sym).
72
+ group(:id).
73
+ reorder(query)
74
+ end
75
+
76
+ def order_by_belongs_to(relation)
77
+ if ordering_by_association_column?(relation)
78
+ order_by_attribute(relation)
79
+ else
80
+ order_by_id(relation)
81
+ end
82
+ end
83
+
84
+ def order_by_has_one(relation)
85
+ if ordering_by_association_column?(relation)
86
+ order_by_attribute(relation)
87
+ else
88
+ order_by_association_id(relation)
89
+ end
90
+ end
91
+
92
+ def order_by_attribute(relation)
93
+ relation.joins(
94
+ attribute.to_sym,
95
+ ).reorder(order_by_attribute_query)
62
96
  end
63
97
 
64
98
  def order_by_id(relation)
65
- relation.reorder("#{attribute}_id #{direction}")
99
+ relation.reorder(order_by_id_query(relation))
100
+ end
101
+
102
+ def order_by_association_id(relation)
103
+ relation.reorder(order_by_association_id_query)
104
+ end
105
+
106
+ def ordering_by_association_column?(relation)
107
+ association_attribute &&
108
+ column_exist?(
109
+ reflect_association(relation).klass, association_attribute.to_sym
110
+ )
111
+ end
112
+
113
+ def column_exist?(table, column_name)
114
+ table.columns_hash.key?(column_name.to_s)
66
115
  end
67
116
 
68
- def has_many_attribute?(relation)
69
- reflect_association(relation).macro == :has_many
117
+ def order_by_id_query(relation)
118
+ relation.arel_table[foreign_key(relation)].public_send(direction)
70
119
  end
71
120
 
72
- def belongs_to_attribute?(relation)
73
- reflect_association(relation).macro == :belongs_to
121
+ def order_by_association_id_query
122
+ Arel::Table.new(association_table_name)[:id].public_send(direction)
123
+ end
124
+
125
+ def order_by_attribute_query
126
+ table = Arel::Table.new(association_table_name)
127
+ table[association_attribute].public_send(direction)
128
+ end
129
+
130
+ def relation_type(relation)
131
+ reflect_association(relation).macro
74
132
  end
75
133
 
76
134
  def reflect_association(relation)
77
135
  relation.klass.reflect_on_association(attribute.to_s)
78
136
  end
137
+
138
+ def foreign_key(relation)
139
+ reflect_association(relation).foreign_key
140
+ end
141
+
142
+ def association_table_name
143
+ attribute.tableize
144
+ end
79
145
  end
80
146
  end
@@ -23,7 +23,11 @@ module Administrate
23
23
  dashboard.try(:item_includes) || []
24
24
  end
25
25
 
26
- protected
26
+ def item_associations
27
+ dashboard.try(:item_associations) || []
28
+ end
29
+
30
+ private
27
31
 
28
32
  def attribute_field(dashboard, resource, attribute_name, page)
29
33
  value = get_attribute_value(resource, attribute_name)
@@ -33,8 +37,6 @@ module Administrate
33
37
 
34
38
  def get_attribute_value(resource, attribute_name)
35
39
  resource.public_send(attribute_name)
36
- rescue NameError
37
- nil
38
40
  end
39
41
 
40
42
  attr_reader :dashboard, :options
@@ -4,6 +4,7 @@ module Administrate
4
4
  module Page
5
5
  class Collection < Page::Base
6
6
  def attribute_names
7
+ options.fetch(:collection_attributes, nil) ||
7
8
  dashboard.collection_attributes
8
9
  end
9
10
 
@@ -10,9 +10,17 @@ module Administrate
10
10
 
11
11
  attr_reader :resource
12
12
 
13
- def attributes
14
- dashboard.form_attributes.map do |attribute|
15
- attribute_field(dashboard, resource, attribute, :form)
13
+ def attributes(action = nil)
14
+ attributes = dashboard.form_attributes(action)
15
+
16
+ if attributes.is_a? Array
17
+ attributes = { "" => attributes }
18
+ end
19
+
20
+ attributes.transform_values do |attrs|
21
+ attrs.map do |attribute|
22
+ attribute_field(dashboard, resource, attribute, :form)
23
+ end
16
24
  end
17
25
  end
18
26
 
@@ -20,7 +28,7 @@ module Administrate
20
28
  dashboard.display_resource(resource)
21
29
  end
22
30
 
23
- protected
31
+ private
24
32
 
25
33
  attr_reader :dashboard
26
34
  end
@@ -15,8 +15,16 @@ module Administrate
15
15
  end
16
16
 
17
17
  def attributes
18
- dashboard.show_page_attributes.map do |attr_name|
19
- attribute_field(dashboard, resource, attr_name, :show)
18
+ attributes = dashboard.show_page_attributes
19
+
20
+ if attributes.is_a? Array
21
+ attributes = { "" => attributes }
22
+ end
23
+
24
+ attributes.transform_values do |attrs|
25
+ attrs.map do |attr_name|
26
+ attribute_field(dashboard, resource, attr_name, :show)
27
+ end
20
28
  end
21
29
  end
22
30
  end