administrate 0.15.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) 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/stylesheets/administrate/application.scss +0 -1
  5. data/app/assets/stylesheets/administrate/base/_forms.scss +1 -1
  6. data/app/assets/stylesheets/administrate/components/_buttons.scss +12 -0
  7. data/app/assets/stylesheets/administrate/components/_flashes.scss +2 -2
  8. data/app/assets/stylesheets/administrate/library/_variables.scss +1 -1
  9. data/app/controllers/administrate/application_controller.rb +95 -17
  10. data/app/controllers/concerns/administrate/punditize.rb +4 -2
  11. data/app/helpers/administrate/application_helper.rb +24 -6
  12. data/app/views/administrate/application/_collection.html.erb +20 -24
  13. data/app/views/administrate/application/_collection_header_actions.html.erb +4 -0
  14. data/app/views/administrate/application/_collection_item_actions.html.erb +17 -0
  15. data/app/views/administrate/application/_flashes.html.erb +1 -0
  16. data/app/views/administrate/application/_form.html.erb +1 -1
  17. data/app/views/administrate/application/_icons.html.erb +1 -1
  18. data/app/views/administrate/application/_index_header.html.erb +28 -0
  19. data/app/views/administrate/application/_navigation.html.erb +2 -2
  20. data/app/views/administrate/application/_pagination.html.erb +1 -0
  21. data/app/views/administrate/application/edit.html.erb +1 -1
  22. data/app/views/administrate/application/index.html.erb +9 -29
  23. data/app/views/administrate/application/show.html.erb +9 -1
  24. data/app/views/fields/belongs_to/_index.html.erb +1 -1
  25. data/app/views/fields/belongs_to/_show.html.erb +1 -1
  26. data/app/views/fields/date/_form.html.erb +1 -3
  27. data/app/views/fields/date_time/_form.html.erb +1 -3
  28. data/app/views/fields/has_many/_index.html.erb +1 -1
  29. data/app/views/fields/has_one/_form.html.erb +1 -1
  30. data/app/views/fields/has_one/_index.html.erb +2 -1
  31. data/app/views/fields/has_one/_show.html.erb +3 -2
  32. data/app/views/fields/polymorphic/_index.html.erb +2 -1
  33. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  34. data/app/views/fields/select/_form.html.erb +4 -2
  35. data/app/views/fields/time/_form.html.erb +2 -3
  36. data/app/views/fields/time/_index.html.erb +1 -1
  37. data/app/views/fields/time/_show.html.erb +1 -1
  38. data/app/views/fields/url/_index.html.erb +2 -2
  39. data/app/views/fields/url/_show.html.erb +2 -2
  40. data/config/locales/administrate.de.yml +2 -2
  41. data/config/locales/administrate.sl.yml +30 -0
  42. data/config/locales/administrate.zh-TW.yml +1 -1
  43. data/docs/adding_controllers_without_related_model.md +2 -4
  44. data/docs/authorization.md +25 -12
  45. data/docs/customizing_controller_actions.md +30 -7
  46. data/docs/customizing_dashboards.md +62 -6
  47. data/docs/extending_administrate.md +5 -5
  48. data/docs/getting_started.md +1 -1
  49. data/docs/guides/customising_search.md +149 -0
  50. data/docs/guides/hiding_dashboards_from_sidebar.md +4 -2
  51. data/docs/guides/scoping_has_many_relations.md +27 -0
  52. data/docs/guides.md +3 -1
  53. data/lib/administrate/base_dashboard.rb +30 -2
  54. data/lib/administrate/engine.rb +2 -2
  55. data/lib/administrate/field/associative.rb +7 -7
  56. data/lib/administrate/field/base.rb +4 -0
  57. data/lib/administrate/field/belongs_to.rb +4 -0
  58. data/lib/administrate/field/deferred.rb +4 -0
  59. data/lib/administrate/field/has_one.rb +4 -0
  60. data/lib/administrate/field/select.rb +4 -0
  61. data/lib/administrate/field/time.rb +11 -0
  62. data/lib/administrate/field/url.rb +4 -0
  63. data/lib/administrate/namespace.rb +1 -1
  64. data/lib/administrate/not_authorized_error.rb +18 -0
  65. data/lib/administrate/order.rb +35 -5
  66. data/lib/administrate/page/base.rb +4 -0
  67. data/lib/administrate/page/form.rb +9 -2
  68. data/lib/administrate/resource_resolver.rb +1 -1
  69. data/lib/administrate/search.rb +21 -17
  70. data/lib/administrate/version.rb +1 -1
  71. data/lib/administrate/view_generator.rb +1 -1
  72. data/lib/administrate.rb +18 -0
  73. data/lib/generators/administrate/dashboard/dashboard_generator.rb +15 -2
  74. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +2 -2
  75. data/lib/generators/administrate/install/templates/application_controller.rb.erb +1 -1
  76. metadata +11 -49
  77. data/app/assets/javascripts/administrate/components/date_time_picker.js +0 -14
  78. data/config/i18n-tasks.yml +0 -18
  79. data/config/routes.rb +0 -2
  80. data/config/unicorn.rb +0 -25
@@ -0,0 +1,27 @@
1
+ ---
2
+ title: Scoping HasMany Relations
3
+ ---
4
+
5
+ To show a subset of a has_many relationship, create a new [has_many](https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many) relationship in your model (using the `scope` argument) and add it to the model's dashboard.
6
+
7
+ ## Creating a scoped has_many relationship
8
+
9
+ Models can define subsets of a `has_many` relationship by passing a callable (i.e. proc or lambda) as its second argument.
10
+
11
+ ```ruby
12
+ class Customer < ApplicationRecord
13
+ has_many :orders
14
+ has_many :processed_orders, ->{ where(processed: true) }, class_name: "Order"
15
+ ```
16
+
17
+ Since ActiveRecord infers the class name from the first argument, the new `has_many` relation needs to specify the model using the `class_name` option.
18
+
19
+ ## Add new relationship to dashboard
20
+
21
+ Your new scoped relation can be used in the dashboard just like the original `HasMany`. Notice the new field needs to specifiy the class name as an option like you did in the model.
22
+
23
+ ```ruby
24
+ ATTRIBUTE_TYPES = {
25
+ orders: Field::HasMany,
26
+ processed_orders: Field::HasMany.with_options(class_name: 'Order')
27
+ ```
data/docs/guides.md CHANGED
@@ -2,4 +2,6 @@
2
2
  title: Guides
3
3
  ---
4
4
 
5
- * [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar)
5
+ - [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar)
6
+ - [Customising the search](./guides/customising_search)
7
+ - [Scoping HasMany Relations](./guides/scoping_has_many_relations.md)
@@ -50,8 +50,16 @@ module Administrate
50
50
  attribute_types.keys
51
51
  end
52
52
 
53
- def form_attributes
54
- self.class::FORM_ATTRIBUTES
53
+ def form_attributes(action = nil)
54
+ specific_form_attributes_for(action) || self.class::FORM_ATTRIBUTES
55
+ end
56
+
57
+ def specific_form_attributes_for(action)
58
+ return unless action
59
+
60
+ cname = "FORM_ATTRIBUTES_#{action.upcase}"
61
+
62
+ self.class.const_get(cname) if self.class.const_defined?(cname)
55
63
  end
56
64
 
57
65
  def permitted_attributes
@@ -71,6 +79,12 @@ module Administrate
71
79
  self.class::COLLECTION_ATTRIBUTES
72
80
  end
73
81
 
82
+ def search_attributes
83
+ attribute_types.keys.select do |attribute|
84
+ attribute_types[attribute].searchable?
85
+ end
86
+ end
87
+
74
88
  def display_resource(resource)
75
89
  "#{resource.class} ##{resource.id}"
76
90
  end
@@ -80,9 +94,15 @@ module Administrate
80
94
  end
81
95
 
82
96
  def item_includes
97
+ # Deprecated, internal usage has moved to #item_associations
98
+ Administrate.warn_of_deprecated_method(self.class, :item_includes)
83
99
  attribute_includes(show_page_attributes)
84
100
  end
85
101
 
102
+ def item_associations
103
+ attribute_associated(show_page_attributes)
104
+ end
105
+
86
106
  private
87
107
 
88
108
  def attribute_not_found_message(attr)
@@ -90,6 +110,14 @@ module Administrate
90
110
  end
91
111
 
92
112
  def attribute_includes(attributes)
113
+ attributes.map do |key|
114
+ field = attribute_type_for(key)
115
+
116
+ key if field.eager_load?
117
+ end.compact
118
+ end
119
+
120
+ def attribute_associated(attributes)
93
121
  attributes.map do |key|
94
122
  field = attribute_type_for(key)
95
123
 
@@ -1,11 +1,11 @@
1
- require "datetime_picker_rails"
2
1
  require "jquery-rails"
3
2
  require "kaminari"
4
- require "momentjs-rails"
5
3
  require "sassc-rails"
6
4
  require "selectize-rails"
7
5
  require "sprockets/railtie"
8
6
 
7
+ require "administrate/namespace/resource"
8
+ require "administrate/not_authorized_error"
9
9
  require "administrate/page/form"
10
10
  require "administrate/page/show"
11
11
  require "administrate/page/collection"
@@ -12,7 +12,7 @@ module Administrate
12
12
  end
13
13
 
14
14
  def self.associated_class_name(resource_class, attr)
15
- reflection(resource_class, attr).class_name
15
+ associated_class(resource_class, attr).name
16
16
  end
17
17
 
18
18
  def self.reflection(resource_class, attr)
@@ -31,12 +31,6 @@ module Administrate
31
31
  end
32
32
  end
33
33
 
34
- private
35
-
36
- def associated_dashboard
37
- "#{associated_class_name}Dashboard".constantize.new
38
- end
39
-
40
34
  def associated_class_name
41
35
  if option_given?(:class_name)
42
36
  deprecated_option(:class_name)
@@ -48,6 +42,12 @@ module Administrate
48
42
  end
49
43
  end
50
44
 
45
+ private
46
+
47
+ def associated_dashboard
48
+ "#{associated_class_name}Dashboard".constantize.new
49
+ end
50
+
51
51
  def primary_key
52
52
  if option_given?(:primary_key)
53
53
  deprecated_option(:primary_key)
@@ -16,6 +16,10 @@ module Administrate
16
16
  self < Associative
17
17
  end
18
18
 
19
+ def self.eager_load?
20
+ false
21
+ end
22
+
19
23
  def self.searchable?
20
24
  false
21
25
  end
@@ -13,6 +13,10 @@ module Administrate
13
13
  end
14
14
  end
15
15
 
16
+ def self.eager_load?
17
+ true
18
+ end
19
+
16
20
  def permitted_attribute
17
21
  foreign_key
18
22
  end
@@ -25,6 +25,10 @@ module Administrate
25
25
  deferred_class.associative?
26
26
  end
27
27
 
28
+ def eager_load?
29
+ deferred_class.eager_load?
30
+ end
31
+
28
32
  def searchable?
29
33
  options.fetch(:searchable, deferred_class.searchable?)
30
34
  end
@@ -26,6 +26,10 @@ module Administrate
26
26
  { "#{attr}_attributes": related_dashboard_attributes }
27
27
  end
28
28
 
29
+ def self.eager_load?
30
+ true
31
+ end
32
+
29
33
  def nested_form
30
34
  @nested_form ||= Administrate::Page::Form.new(
31
35
  resolver.dashboard_class.new,
@@ -11,6 +11,10 @@ module Administrate
11
11
  collection
12
12
  end
13
13
 
14
+ def include_blank_option
15
+ options.fetch(:include_blank, false)
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def collection
@@ -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
@@ -0,0 +1,18 @@
1
+ module Administrate
2
+ class NotAuthorizedError < StandardError
3
+ def initialize(action:, resource:)
4
+ @action = action
5
+ @resource = resource
6
+
7
+ case resource
8
+ when Module, String, Symbol
9
+ super("Not allowed to perform #{action.inspect} on #{resource.inspect}")
10
+ else
11
+ super(
12
+ "Not allowed to perform #{action.inspect} on the given " +
13
+ resource.class.name
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,8 +1,9 @@
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
5
  @direction = sanitize_direction(direction)
6
+ @association_attribute = association_attribute
6
7
  end
7
8
 
8
9
  def apply(relation)
@@ -12,7 +13,7 @@ module Administrate
12
13
  order = "#{relation.table_name}.#{attribute} #{direction}"
13
14
 
14
15
  return relation.reorder(Arel.sql(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,7 @@ module Administrate
32
33
 
33
34
  private
34
35
 
35
- attr_reader :attribute
36
+ attr_reader :attribute, :association_attribute
36
37
 
37
38
  def sanitize_direction(direction)
38
39
  %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
@@ -53,7 +54,7 @@ module Administrate
53
54
  def order_by_association(relation)
54
55
  return order_by_count(relation) if has_many_attribute?(relation)
55
56
 
56
- return order_by_id(relation) if belongs_to_attribute?(relation)
57
+ return order_by_attribute(relation) if belongs_to_attribute?(relation)
57
58
 
58
59
  relation
59
60
  end
@@ -68,7 +69,36 @@ module Administrate
68
69
  end
69
70
 
70
71
  def order_by_id(relation)
71
- relation.reorder("#{foreign_key(relation)} #{direction}")
72
+ relation.reorder(Arel.sql(order_by_id_query(relation)))
73
+ end
74
+
75
+ def order_by_attribute(relation)
76
+ if ordering_by_association_column?(relation)
77
+ relation.joins(
78
+ attribute.to_sym,
79
+ ).reorder(Arel.sql(order_by_attribute_query))
80
+ else
81
+ order_by_id(relation)
82
+ end
83
+ end
84
+
85
+ def ordering_by_association_column?(relation)
86
+ association_attribute &&
87
+ column_exist?(
88
+ reflect_association(relation).klass, association_attribute.to_sym
89
+ )
90
+ end
91
+
92
+ def column_exist?(table, column_name)
93
+ table.columns_hash.key?(column_name.to_s)
94
+ end
95
+
96
+ def order_by_id_query(relation)
97
+ "#{relation.table_name}.#{foreign_key(relation)} #{direction}"
98
+ end
99
+
100
+ def order_by_attribute_query
101
+ "#{attribute.tableize}.#{association_attribute} #{direction}"
72
102
  end
73
103
 
74
104
  def has_many_attribute?(relation)
@@ -23,6 +23,10 @@ module Administrate
23
23
  dashboard.try(:item_includes) || []
24
24
  end
25
25
 
26
+ def item_associations
27
+ dashboard.try(:item_associations) || []
28
+ end
29
+
26
30
  private
27
31
 
28
32
  def attribute_field(dashboard, resource, attribute_name, page)
@@ -10,8 +10,15 @@ module Administrate
10
10
 
11
11
  attr_reader :resource
12
12
 
13
- def attributes
14
- dashboard.form_attributes.map do |attribute|
13
+ def attributes(action = nil)
14
+ action =
15
+ case action
16
+ when "update" then "edit"
17
+ when "create" then "new"
18
+ else action
19
+ end
20
+
21
+ dashboard.form_attributes(action).map do |attribute|
15
22
  attribute_field(dashboard, resource, attribute, :form)
16
23
  end
17
24
  end
@@ -9,7 +9,7 @@ module Administrate
9
9
  end
10
10
 
11
11
  def namespace
12
- controller_path.split("/").first
12
+ controller_path.split("/").first.to_sym
13
13
  end
14
14
 
15
15
  def resource_class
@@ -4,14 +4,15 @@ require "active_support/core_ext/object/blank"
4
4
  module Administrate
5
5
  class Search
6
6
  class Query
7
- attr_reader :filters
7
+ attr_reader :filters, :valid_filters
8
8
 
9
9
  def blank?
10
10
  terms.blank? && filters.empty?
11
11
  end
12
12
 
13
- def initialize(original_query)
13
+ def initialize(original_query, valid_filters = nil)
14
14
  @original_query = original_query
15
+ @valid_filters = valid_filters
15
16
  @filters, @terms = parse_query(original_query)
16
17
  end
17
18
 
@@ -30,7 +31,7 @@ module Administrate
30
31
  private
31
32
 
32
33
  def filter?(word)
33
- word.match?(/^\w+:$/)
34
+ valid_filters&.any? { |filter| word.match?(/^#{filter}:\w*$/) }
34
35
  end
35
36
 
36
37
  def parse_query(query)
@@ -38,7 +39,7 @@ module Administrate
38
39
  terms = []
39
40
  query.to_s.split.each do |word|
40
41
  if filter?(word)
41
- filters << word.split(":").first
42
+ filters << word
42
43
  else
43
44
  terms << word
44
45
  end
@@ -47,10 +48,10 @@ module Administrate
47
48
  end
48
49
  end
49
50
 
50
- def initialize(scoped_resource, dashboard_class, term)
51
- @dashboard_class = dashboard_class
51
+ def initialize(scoped_resource, dashboard, term)
52
+ @dashboard = dashboard
52
53
  @scoped_resource = scoped_resource
53
- @query = Query.new(term)
54
+ @query = Query.new(term, valid_filters.keys)
54
55
  end
55
56
 
56
57
  def run
@@ -65,15 +66,20 @@ module Administrate
65
66
 
66
67
  private
67
68
 
68
- def apply_filter(filter, resources)
69
+ def apply_filter(filter, filter_param, resources)
69
70
  return resources unless filter
70
- filter.call(resources)
71
+ if filter.parameters.size == 1
72
+ filter.call(resources)
73
+ else
74
+ filter.call(resources, filter_param)
75
+ end
71
76
  end
72
77
 
73
78
  def filter_results(resources)
74
- query.filters.each do |filter_name|
79
+ query.filters.each do |filter_query|
80
+ filter_name, filter_param = filter_query.split(":")
75
81
  filter = valid_filters[filter_name]
76
- resources = apply_filter(filter, resources)
82
+ resources = apply_filter(filter, filter_param, resources)
77
83
  end
78
84
  resources
79
85
  end
@@ -102,9 +108,7 @@ module Administrate
102
108
  end
103
109
 
104
110
  def search_attributes
105
- attribute_types.keys.select do |attribute|
106
- attribute_types[attribute].searchable?
107
- end
111
+ @dashboard.search_attributes
108
112
  end
109
113
 
110
114
  def search_results(resources)
@@ -114,15 +118,15 @@ module Administrate
114
118
  end
115
119
 
116
120
  def valid_filters
117
- if @dashboard_class.const_defined?(:COLLECTION_FILTERS)
118
- @dashboard_class.const_get(:COLLECTION_FILTERS).stringify_keys
121
+ if @dashboard.class.const_defined?(:COLLECTION_FILTERS)
122
+ @dashboard.class.const_get(:COLLECTION_FILTERS).stringify_keys
119
123
  else
120
124
  {}
121
125
  end
122
126
  end
123
127
 
124
128
  def attribute_types
125
- @dashboard_class::ATTRIBUTE_TYPES
129
+ @dashboard.class.const_get(:ATTRIBUTE_TYPES)
126
130
  end
127
131
 
128
132
  def query_table_name(attr)
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.15.0".freeze
2
+ VERSION = "0.18.0".freeze
3
3
  end
@@ -5,7 +5,7 @@ require "administrate/namespace"
5
5
  module Administrate
6
6
  class ViewGenerator < Rails::Generators::Base
7
7
  include Administrate::GeneratorHelpers
8
- class_option :namespace, type: :string, default: "admin"
8
+ class_option :namespace, type: :string, default: :admin
9
9
 
10
10
  def self.template_source_path
11
11
  File.expand_path(
data/lib/administrate.rb CHANGED
@@ -20,4 +20,22 @@ module Administrate
20
20
  "if you think otherwise.",
21
21
  )
22
22
  end
23
+
24
+ def self.warn_of_deprecated_method(klass, method)
25
+ ActiveSupport::Deprecation.warn(
26
+ "The method #{klass}##{method} is deprecated. " +
27
+ "If you are seeing this message you are probably " +
28
+ "using a dashboard that depends explicitly on it. " +
29
+ "Please make sure you update it to a version that " +
30
+ "does not use a deprecated API",
31
+ )
32
+ end
33
+
34
+ def self.warn_of_deprecated_authorization_method(method)
35
+ ActiveSupport::Deprecation.warn(
36
+ "The method `#{method}` is deprecated. " +
37
+ "Please use `accessible_action?` instead, " +
38
+ "or see the documentation for other options.",
39
+ )
40
+ end
23
41
  end
@@ -27,7 +27,7 @@ module Administrate
27
27
  COLLECTION_ATTRIBUTE_LIMIT = 4
28
28
  READ_ONLY_ATTRIBUTES = %w[id created_at updated_at]
29
29
 
30
- class_option :namespace, type: :string, default: "admin"
30
+ class_option :namespace, type: :string, default: :admin
31
31
 
32
32
  source_root File.expand_path("../templates", __FILE__)
33
33
 
@@ -53,9 +53,22 @@ module Administrate
53
53
  end
54
54
 
55
55
  def attributes
56
- klass.reflections.keys +
56
+ attrs = (
57
+ klass.reflections.keys +
57
58
  klass.columns.map(&:name) -
58
59
  redundant_attributes
60
+ )
61
+
62
+ primary_key = attrs.delete(klass.primary_key)
63
+ created_at = attrs.delete("created_at")
64
+ updated_at = attrs.delete("updated_at")
65
+
66
+ [
67
+ primary_key,
68
+ *attrs.sort,
69
+ created_at,
70
+ updated_at,
71
+ ].compact
59
72
  end
60
73
 
61
74
  def form_attributes
@@ -1,5 +1,5 @@
1
- module <%= namespace.classify %>
2
- class <%= class_name.pluralize %>Controller < <%= namespace.classify %>::ApplicationController
1
+ module <%= namespace.to_s.camelize %>
2
+ class <%= class_name.pluralize %>Controller < <%= namespace.to_s.camelize %>::ApplicationController
3
3
  # Overwrite any of the RESTful controller actions to implement custom behavior
4
4
  # For example, you may want to send an email after a foo is updated.
5
5
  #
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # If you want to add pagination or other controller-level concerns,
6
6
  # you're free to overwrite the RESTful controller actions.
7
- module <%= namespace.classify %>
7
+ module <%= namespace.camelize %>
8
8
  class ApplicationController < Administrate::ApplicationController
9
9
  before_action :authenticate_admin
10
10