administrate 0.9.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/{has_many_form.js → associative.js} +1 -0
  3. data/app/assets/javascripts/administrate/components/date_time_picker.js +10 -2
  4. data/app/assets/javascripts/administrate/components/table.js +1 -1
  5. data/app/assets/stylesheets/administrate/base/_tables.scss +3 -0
  6. data/app/assets/stylesheets/administrate/base/_typography.scss +1 -1
  7. data/app/assets/stylesheets/administrate/components/_attributes.scss +1 -1
  8. data/app/assets/stylesheets/administrate/components/_buttons.scss +8 -0
  9. data/app/assets/stylesheets/administrate/components/_cells.scss +2 -0
  10. data/app/assets/stylesheets/administrate/components/_field-unit.scss +13 -4
  11. data/app/assets/stylesheets/administrate/components/_flashes.scss +0 -8
  12. data/app/assets/stylesheets/administrate/components/_navigation.scss +2 -3
  13. data/app/assets/stylesheets/administrate/library/_variables.scss +10 -8
  14. data/app/controllers/administrate/application_controller.rb +42 -13
  15. data/app/controllers/concerns/administrate/punditize.rb +3 -3
  16. data/app/helpers/administrate/application_helper.rb +59 -14
  17. data/app/views/administrate/application/_collection.html.erb +7 -6
  18. data/app/views/administrate/application/_flashes.html.erb +1 -1
  19. data/app/views/administrate/application/_form.html.erb +2 -2
  20. data/app/views/administrate/application/{_icons.erb → _icons.html.erb} +0 -0
  21. data/app/views/administrate/application/_navigation.html.erb +5 -3
  22. data/app/views/administrate/application/index.html.erb +2 -0
  23. data/app/views/administrate/application/show.html.erb +1 -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 +24 -0
  27. data/app/views/fields/date/_index.html.erb +21 -0
  28. data/app/views/fields/date/_show.html.erb +21 -0
  29. data/app/views/fields/date_time/_form.html.erb +1 -1
  30. data/app/views/fields/date_time/_index.html.erb +1 -1
  31. data/app/views/fields/has_many/_form.html.erb +2 -2
  32. data/app/views/fields/has_many/_show.html.erb +8 -5
  33. data/app/views/fields/has_one/_form.html.erb +1 -1
  34. data/app/views/fields/password/_form.html.erb +23 -0
  35. data/app/views/fields/password/_index.html.erb +18 -0
  36. data/app/views/fields/password/_show.html.erb +18 -0
  37. data/app/views/fields/polymorphic/_form.html.erb +0 -3
  38. data/app/views/fields/select/_form.html.erb +22 -10
  39. data/app/views/fields/string/_show.html.erb +2 -2
  40. data/app/views/fields/text/_show.html.erb +2 -3
  41. data/app/views/fields/time/_form.html.erb +23 -0
  42. data/app/views/fields/time/_index.html.erb +17 -0
  43. data/app/views/fields/time/_show.html.erb +17 -0
  44. data/app/views/fields/url/_form.html.erb +23 -0
  45. data/app/views/fields/url/_index.html.erb +20 -0
  46. data/app/views/fields/url/_show.html.erb +20 -0
  47. data/app/views/layouts/administrate/application.html.erb +1 -1
  48. data/config/locales/administrate.ar.yml +2 -0
  49. data/config/locales/administrate.bs.yml +2 -0
  50. data/config/locales/administrate.ca.yml +2 -0
  51. data/config/locales/administrate.da.yml +2 -0
  52. data/config/locales/administrate.de.yml +2 -0
  53. data/config/locales/administrate.en.yml +2 -0
  54. data/config/locales/administrate.es.yml +3 -1
  55. data/config/locales/administrate.fr.yml +2 -0
  56. data/config/locales/administrate.id.yml +30 -0
  57. data/config/locales/administrate.it.yml +2 -0
  58. data/config/locales/administrate.ja.yml +4 -2
  59. data/config/locales/administrate.ko.yml +12 -10
  60. data/config/locales/administrate.nl.yml +3 -1
  61. data/config/locales/administrate.pl.yml +2 -0
  62. data/config/locales/administrate.pt-BR.yml +2 -0
  63. data/config/locales/administrate.pt.yml +2 -0
  64. data/config/locales/administrate.ru.yml +4 -2
  65. data/config/locales/administrate.sq.yml +30 -0
  66. data/config/locales/administrate.sv.yml +2 -0
  67. data/config/locales/administrate.uk.yml +2 -0
  68. data/config/locales/administrate.vi.yml +2 -0
  69. data/config/locales/administrate.zh-CN.yml +4 -2
  70. data/config/locales/administrate.zh-TW.yml +6 -4
  71. data/docs/adding_controllers_without_related_model.md +36 -0
  72. data/docs/adding_custom_field_types.md +3 -1
  73. data/docs/authentication.md +3 -1
  74. data/docs/authorization.md +5 -3
  75. data/docs/contributing.md +1 -0
  76. data/docs/customizing_attribute_partials.md +5 -2
  77. data/docs/customizing_controller_actions.md +32 -2
  78. data/docs/customizing_dashboards.md +103 -13
  79. data/docs/customizing_page_views.md +23 -5
  80. data/docs/getting_started.md +22 -51
  81. data/docs/rails_api.md +45 -0
  82. data/lib/administrate/base_dashboard.rb +33 -8
  83. data/lib/administrate/custom_dashboard.rb +15 -0
  84. data/lib/administrate/engine.rb +1 -1
  85. data/lib/administrate/field/associative.rb +5 -5
  86. data/lib/administrate/field/base.rb +13 -13
  87. data/lib/administrate/field/belongs_to.rb +3 -1
  88. data/lib/administrate/field/date.rb +20 -0
  89. data/lib/administrate/field/date_time.rb +1 -1
  90. data/lib/administrate/field/deferred.rb +19 -0
  91. data/lib/administrate/field/has_many.rb +16 -9
  92. data/lib/administrate/field/has_one.rb +7 -7
  93. data/lib/administrate/field/password.rb +25 -0
  94. data/lib/administrate/field/polymorphic.rb +6 -6
  95. data/lib/administrate/field/select.rb +6 -1
  96. data/lib/administrate/field/time.rb +8 -0
  97. data/lib/administrate/field/url.rb +21 -0
  98. data/lib/administrate/namespace.rb +5 -1
  99. data/lib/administrate/order.rb +17 -7
  100. data/lib/administrate/page/base.rb +9 -3
  101. data/lib/administrate/page/collection.rb +5 -1
  102. data/lib/administrate/page/form.rb +1 -1
  103. data/lib/administrate/search.rb +126 -11
  104. data/lib/administrate/version.rb +1 -1
  105. data/lib/administrate/view_generator.rb +2 -2
  106. data/lib/generators/administrate/dashboard/dashboard_generator.rb +19 -6
  107. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +33 -8
  108. data/lib/generators/administrate/dashboard/templates/dashboard.rb.erb +18 -6
  109. data/lib/generators/administrate/install/templates/application_controller.rb.erb +3 -3
  110. data/lib/generators/administrate/routes/routes_generator.rb +18 -13
  111. data/lib/generators/administrate/views/layout_generator.rb +1 -0
  112. metadata +47 -31
  113. data/config/secrets.yml +0 -14
@@ -12,12 +12,16 @@ module Administrate
12
12
 
13
13
  def routes
14
14
  @routes ||= all_routes.select do |controller, _action|
15
- controller.starts_with?(namespace.to_s)
15
+ controller.starts_with?("#{namespace}/")
16
16
  end.map do |controller, action|
17
17
  [controller.gsub(/^#{namespace}\//, ""), action]
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
@@ -2,14 +2,16 @@ module Administrate
2
2
  class Order
3
3
  def initialize(attribute = nil, direction = nil)
4
4
  @attribute = attribute
5
- @direction = direction || :asc
5
+ @direction = sanitize_direction(direction)
6
6
  end
7
7
 
8
8
  def apply(relation)
9
9
  return order_by_association(relation) unless
10
10
  reflect_association(relation).nil?
11
11
 
12
- return relation.reorder("#{attribute} #{direction}") if
12
+ order = "#{relation.table_name}.#{attribute} #{direction}"
13
+
14
+ return relation.reorder(Arel.sql(order)) if
13
15
  relation.columns_hash.keys.include?(attribute.to_s)
14
16
 
15
17
  relation
@@ -32,6 +34,10 @@ module Administrate
32
34
 
33
35
  attr_reader :attribute
34
36
 
37
+ def sanitize_direction(direction)
38
+ %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
39
+ end
40
+
35
41
  def reversed_direction_param_for(attr)
36
42
  if ordered_by?(attr)
37
43
  opposite_direction
@@ -41,7 +47,7 @@ module Administrate
41
47
  end
42
48
 
43
49
  def opposite_direction
44
- direction.to_sym == :asc ? :desc : :asc
50
+ direction == :asc ? :desc : :asc
45
51
  end
46
52
 
47
53
  def order_by_association(relation)
@@ -54,13 +60,13 @@ module Administrate
54
60
 
55
61
  def order_by_count(relation)
56
62
  relation.
57
- left_joins(attribute.to_sym).
58
- group(:id).
59
- reorder("COUNT(#{attribute}.id) #{direction}")
63
+ left_joins(attribute.to_sym).
64
+ group(:id).
65
+ reorder("COUNT(#{attribute}.id) #{direction}")
60
66
  end
61
67
 
62
68
  def order_by_id(relation)
63
- relation.reorder("#{attribute}_id #{direction}")
69
+ relation.reorder("#{foreign_key(relation)} #{direction}")
64
70
  end
65
71
 
66
72
  def has_many_attribute?(relation)
@@ -74,5 +80,9 @@ module Administrate
74
80
  def reflect_association(relation)
75
81
  relation.klass.reflect_on_association(attribute.to_s)
76
82
  end
83
+
84
+ def foreign_key(relation)
85
+ reflect_association(relation).foreign_key
86
+ end
77
87
  end
78
88
  end
@@ -15,7 +15,15 @@ module Administrate
15
15
  @resource_path ||= resource_name.gsub("/", "_")
16
16
  end
17
17
 
18
- protected
18
+ def collection_includes
19
+ dashboard.try(:collection_includes) || []
20
+ end
21
+
22
+ def item_includes
23
+ dashboard.try(:item_includes) || []
24
+ end
25
+
26
+ private
19
27
 
20
28
  def attribute_field(dashboard, resource, attribute_name, page)
21
29
  value = get_attribute_value(resource, attribute_name)
@@ -25,8 +33,6 @@ module Administrate
25
33
 
26
34
  def get_attribute_value(resource, attribute_name)
27
35
  resource.public_send(attribute_name)
28
- rescue NameError
29
- nil
30
36
  end
31
37
 
32
38
  attr_reader :dashboard, :options
@@ -21,7 +21,11 @@ module Administrate
21
21
  ordered_by?(attr) && order.direction
22
22
  end
23
23
 
24
- delegate :ordered_by?, :order_params_for, to: :order
24
+ delegate :ordered_by?, to: :order
25
+
26
+ def order_params_for(attr, key: resource_name)
27
+ { key => order.order_params_for(attr) }
28
+ end
25
29
 
26
30
  private
27
31
 
@@ -20,7 +20,7 @@ module Administrate
20
20
  dashboard.display_resource(resource)
21
21
  end
22
22
 
23
- protected
23
+ private
24
24
 
25
25
  attr_reader :dashboard
26
26
  end
@@ -3,33 +3,102 @@ require "active_support/core_ext/object/blank"
3
3
 
4
4
  module Administrate
5
5
  class Search
6
+ class Query
7
+ attr_reader :filters
8
+
9
+ def blank?
10
+ terms.blank? && filters.empty?
11
+ end
12
+
13
+ def initialize(original_query)
14
+ @original_query = original_query
15
+ @filters, @terms = parse_query(original_query)
16
+ end
17
+
18
+ def original
19
+ @original_query
20
+ end
21
+
22
+ def terms
23
+ @terms.join(" ")
24
+ end
25
+
26
+ def to_s
27
+ original
28
+ end
29
+
30
+ private
31
+
32
+ def filter?(word)
33
+ word.match?(/^\w+:$/)
34
+ end
35
+
36
+ def parse_query(query)
37
+ filters = []
38
+ terms = []
39
+ query.to_s.split.each do |word|
40
+ if filter?(word)
41
+ filters << word.split(":").first
42
+ else
43
+ terms << word
44
+ end
45
+ end
46
+ [filters, terms]
47
+ end
48
+ end
49
+
6
50
  def initialize(scoped_resource, dashboard_class, term)
7
51
  @dashboard_class = dashboard_class
8
52
  @scoped_resource = scoped_resource
9
- @term = term
53
+ @query = Query.new(term)
10
54
  end
11
55
 
12
56
  def run
13
- if @term.blank?
57
+ if query.blank?
14
58
  @scoped_resource.all
15
59
  else
16
- @scoped_resource.where(query, *search_terms)
60
+ results = search_results(@scoped_resource)
61
+ results = filter_results(results)
62
+ results
17
63
  end
18
64
  end
19
65
 
20
66
  private
21
67
 
22
- def query
68
+ def apply_filter(filter, resources)
69
+ return resources unless filter
70
+ filter.call(resources)
71
+ end
72
+
73
+ def filter_results(resources)
74
+ query.filters.each do |filter_name|
75
+ filter = valid_filters[filter_name]
76
+ resources = apply_filter(filter, resources)
77
+ end
78
+ resources
79
+ end
80
+
81
+ def query_template
23
82
  search_attributes.map do |attr|
24
- table_name = ActiveRecord::Base.connection.
25
- quote_table_name(@scoped_resource.table_name)
26
- attr_name = ActiveRecord::Base.connection.quote_column_name(attr)
27
- "LOWER(TEXT(#{table_name}.#{attr_name})) LIKE ?"
83
+ table_name = query_table_name(attr)
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 ?"
87
+ end.join(" OR ")
28
88
  end.join(" OR ")
29
89
  end
30
90
 
31
- def search_terms
32
- ["%#{term.mb_chars.downcase}%"] * search_attributes.count
91
+ def searchable_fields(attr)
92
+ return [attr] unless association_search?(attr)
93
+
94
+ attribute_types[attr].searchable_fields
95
+ end
96
+
97
+ def query_values
98
+ fields_count = search_attributes.sum do |attr|
99
+ searchable_fields(attr).count
100
+ end
101
+ ["%#{term.mb_chars.downcase}%"] * fields_count
33
102
  end
34
103
 
35
104
  def search_attributes
@@ -38,10 +107,56 @@ module Administrate
38
107
  end
39
108
  end
40
109
 
110
+ def search_results(resources)
111
+ resources.
112
+ joins(tables_to_join).
113
+ where(query_template, *query_values)
114
+ end
115
+
116
+ def valid_filters
117
+ if @dashboard_class.const_defined?(:COLLECTION_FILTERS)
118
+ @dashboard_class.const_get(:COLLECTION_FILTERS).stringify_keys
119
+ else
120
+ {}
121
+ end
122
+ end
123
+
41
124
  def attribute_types
42
125
  @dashboard_class::ATTRIBUTE_TYPES
43
126
  end
44
127
 
45
- attr_reader :resolver, :term
128
+ def query_table_name(attr)
129
+ if association_search?(attr)
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
136
+ else
137
+ ActiveRecord::Base.connection.
138
+ quote_table_name(@scoped_resource.table_name)
139
+ end
140
+ end
141
+
142
+ def column_to_query(attr)
143
+ ActiveRecord::Base.connection.quote_column_name(attr)
144
+ end
145
+
146
+ def tables_to_join
147
+ attribute_types.keys.select do |attribute|
148
+ attribute_types[attribute].searchable? && association_search?(attribute)
149
+ end
150
+ end
151
+
152
+ def association_search?(attribute)
153
+ attribute_types[attribute].associative?
154
+ end
155
+
156
+ def term
157
+ query.terms
158
+ end
159
+
160
+ attr_reader :resolver, :query
46
161
  end
47
162
  end
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.9.0".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
@@ -5,8 +5,6 @@ module Administrate
5
5
  class ViewGenerator < Rails::Generators::Base
6
6
  include Administrate::GeneratorHelpers
7
7
 
8
- private
9
-
10
8
  def self.template_source_path
11
9
  File.expand_path(
12
10
  "../../../app/views/administrate/application",
@@ -14,6 +12,8 @@ module Administrate
14
12
  )
15
13
  end
16
14
 
15
+ private
16
+
17
17
  def copy_resource_template(template_name)
18
18
  template_file = "#{template_name}.html.erb"
19
19
 
@@ -5,18 +5,20 @@ module Administrate
5
5
  class DashboardGenerator < Rails::Generators::NamedBase
6
6
  ATTRIBUTE_TYPE_MAPPING = {
7
7
  boolean: "Field::Boolean",
8
- date: "Field::DateTime",
8
+ date: "Field::Date",
9
9
  datetime: "Field::DateTime",
10
- enum: "Field::String",
10
+ enum: "Field::Select",
11
11
  float: "Field::Number",
12
12
  integer: "Field::Number",
13
- time: "Field::DateTime",
13
+ time: "Field::Time",
14
14
  text: "Field::Text",
15
15
  string: "Field::String",
16
16
  }
17
17
 
18
18
  ATTRIBUTE_OPTIONS_MAPPING = {
19
- enum: { searchable: false },
19
+ # procs must be defined in one line!
20
+ enum: { searchable: false,
21
+ collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys } },
20
22
  float: { decimals: 2 },
21
23
  }
22
24
 
@@ -50,7 +52,9 @@ module Administrate
50
52
  end
51
53
 
52
54
  def attributes
53
- klass.reflections.keys + klass.attribute_names - redundant_attributes
55
+ klass.reflections.keys +
56
+ klass.columns.map(&:name) -
57
+ redundant_attributes
54
58
  end
55
59
 
56
60
  def form_attributes
@@ -134,7 +138,16 @@ module Administrate
134
138
  end
135
139
 
136
140
  def inspect_hash_as_ruby(hash)
137
- hash.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
141
+ hash.map do |key, value|
142
+ v_str = value.respond_to?(:call) ? proc_string(value) : value.inspect
143
+ "#{key}: #{v_str}"
144
+ end.join(", ")
145
+ end
146
+
147
+ def proc_string(value)
148
+ source = value.source_location
149
+ proc_string = IO.readlines(source.first)[source.second - 1]
150
+ proc_string[/->[^}]*} | (lambda|proc).*end/x]
138
151
  end
139
152
  end
140
153
  end
@@ -1,18 +1,43 @@
1
1
  module <%= namespace.classify %>
2
2
  class <%= class_name.pluralize %>Controller < <%= namespace.classify %>::ApplicationController
3
- # To customize the behavior of this controller,
4
- # you can overwrite any of the RESTful actions. For example:
3
+ # Overwrite any of the RESTful controller actions to implement custom behavior
4
+ # For example, you may want to send an email after a foo is updated.
5
5
  #
6
- # def index
6
+ # def update
7
7
  # super
8
- # @resources = <%= class_name %>.
9
- # page(params[:page]).
10
- # per(10)
8
+ # send_foo_updated_email(requested_resource)
11
9
  # end
12
10
 
13
- # Define a custom finder by overriding the `find_resource` method:
11
+ # Override this method to specify custom lookup behavior.
12
+ # This will be used to set the resource for the `show`, `edit`, and `update`
13
+ # actions.
14
+ #
14
15
  # def find_resource(param)
15
- # <%= class_name %>.find_by!(slug: param)
16
+ # Foo.find_by!(slug: param)
17
+ # end
18
+
19
+ # The result of this lookup will be available as `requested_resource`
20
+
21
+ # Override this if you have certain roles that require a subset
22
+ # this will be used to set the records shown on the `index` action.
23
+ #
24
+ # def scoped_resource
25
+ # if current_user.super_admin?
26
+ # resource_class
27
+ # else
28
+ # resource_class.with_less_stuff
29
+ # end
30
+ # end
31
+
32
+ # Override `resource_params` if you want to transform the submitted
33
+ # data before it's persisted. For example, the following would turn all
34
+ # empty values into nil values. It uses other APIs such as `resource_class`
35
+ # and `dashboard`:
36
+ #
37
+ # def resource_params
38
+ # params.require(resource_class.model_name.param_key).
39
+ # permit(dashboard.permitted_attributes).
40
+ # transform_values { |value| value == "" ? nil : value }
16
41
  # end
17
42
 
18
43
  # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
@@ -18,20 +18,20 @@ class <%= class_name %>Dashboard < Administrate::BaseDashboard
18
18
  #
19
19
  # By default, it's limited to four items to reduce clutter on index pages.
20
20
  # Feel free to add, remove, or rearrange items.
21
- COLLECTION_ATTRIBUTES = [
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
28
28
 
29
29
  # SHOW_PAGE_ATTRIBUTES
30
30
  # an array of attributes that will be displayed on the model's show page.
31
- SHOW_PAGE_ATTRIBUTES = [
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
@@ -39,14 +39,26 @@ class <%= class_name %>Dashboard < Administrate::BaseDashboard
39
39
  # FORM_ATTRIBUTES
40
40
  # an array of attributes that will be displayed
41
41
  # on the model's form (`new` and `edit`) pages.
42
- FORM_ATTRIBUTES = [
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
49
49
 
50
+ # COLLECTION_FILTERS
51
+ # a hash that defines filters that can be used while searching via the search
52
+ # field of the dashboard.
53
+ #
54
+ # For example to add an option to search for open resources by typing "open:"
55
+ # in the search field:
56
+ #
57
+ # COLLECTION_FILTERS = {
58
+ # open: ->(resources) { resources.where(open: true) }
59
+ # }.freeze
60
+ COLLECTION_FILTERS = {}.freeze
61
+
50
62
  # Overwrite this method to customize how <%= file_name.pluralize.humanize.downcase %> are displayed
51
63
  # across all pages of the admin dashboard.
52
64
  #