madmin 0.1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +50 -63
  4. data/Rakefile +5 -6
  5. data/app/assets/config/manifest.js +2 -0
  6. data/app/assets/stylesheets/actiontext.scss +36 -0
  7. data/app/assets/stylesheets/{madmin/application.css → application.css} +2 -9
  8. data/app/controllers/madmin/base_controller.rb +5 -9
  9. data/app/controllers/madmin/dashboard_controller.rb +1 -3
  10. data/app/controllers/madmin/resource_controller.rb +81 -0
  11. data/app/helpers/madmin/application_helper.rb +10 -8
  12. data/app/views/layouts/madmin/application.html.erb +23 -25
  13. data/app/views/madmin/application/_form.html.erb +25 -0
  14. data/app/views/madmin/application/_javascript.html.erb +24 -0
  15. data/app/views/madmin/application/_navigation.html.erb +6 -0
  16. data/app/views/madmin/application/edit.html.erb +3 -0
  17. data/app/views/madmin/application/index.html.erb +47 -0
  18. data/app/views/madmin/application/new.html.erb +3 -0
  19. data/app/views/madmin/application/show.html.erb +24 -0
  20. data/app/views/madmin/dashboard/show.html.erb +1 -0
  21. data/app/views/madmin/fields/attachment/_form.html.erb +2 -0
  22. data/app/views/madmin/fields/attachment/_index.html.erb +3 -0
  23. data/app/views/madmin/fields/attachment/_show.html.erb +3 -0
  24. data/app/views/madmin/fields/attachments/_form.html.erb +2 -0
  25. data/app/views/madmin/fields/attachments/_index.html.erb +1 -0
  26. data/app/views/madmin/fields/attachments/_show.html.erb +7 -0
  27. data/app/views/madmin/fields/belongs_to/_form.html.erb +2 -14
  28. data/app/views/madmin/fields/belongs_to/_index.html.erb +3 -7
  29. data/app/views/madmin/fields/belongs_to/_show.html.erb +3 -8
  30. data/app/views/madmin/fields/boolean/_form.html.erb +2 -0
  31. data/app/views/madmin/fields/boolean/_index.html.erb +1 -0
  32. data/app/views/madmin/fields/boolean/_show.html.erb +1 -0
  33. data/app/views/madmin/fields/date/_form.html.erb +2 -0
  34. data/app/views/madmin/fields/date/_index.html.erb +1 -0
  35. data/app/views/madmin/fields/date/_show.html.erb +1 -0
  36. data/app/views/madmin/fields/date_time/_form.html.erb +2 -0
  37. data/app/views/madmin/fields/date_time/_index.html.erb +1 -0
  38. data/app/views/madmin/fields/date_time/_show.html.erb +1 -0
  39. data/app/views/madmin/fields/decimal/_form.html.erb +2 -0
  40. data/app/views/madmin/fields/decimal/_index.html.erb +1 -0
  41. data/app/views/madmin/fields/decimal/_show.html.erb +1 -0
  42. data/app/views/madmin/fields/enum/_form.html.erb +2 -0
  43. data/app/views/madmin/fields/enum/_index.html.erb +1 -0
  44. data/app/views/madmin/fields/enum/_show.html.erb +1 -0
  45. data/app/views/madmin/fields/float/_form.html.erb +2 -0
  46. data/app/views/madmin/fields/float/_index.html.erb +1 -0
  47. data/app/views/madmin/fields/float/_show.html.erb +1 -0
  48. data/app/views/madmin/fields/has_many/_form.html.erb +2 -0
  49. data/app/views/madmin/fields/has_many/_index.html.erb +1 -0
  50. data/app/views/madmin/fields/has_many/_show.html.erb +4 -14
  51. data/app/views/madmin/fields/has_one/_form.html.erb +3 -0
  52. data/app/views/madmin/fields/has_one/_index.html.erb +3 -0
  53. data/app/views/madmin/fields/has_one/_show.html.erb +3 -12
  54. data/app/views/madmin/fields/integer/_form.html.erb +2 -0
  55. data/app/views/madmin/fields/integer/_index.html.erb +1 -0
  56. data/app/views/madmin/fields/integer/_show.html.erb +1 -0
  57. data/app/views/madmin/fields/json/_form.html.erb +2 -0
  58. data/app/views/madmin/fields/json/_index.html.erb +1 -0
  59. data/app/views/madmin/fields/json/_show.html.erb +1 -0
  60. data/app/views/madmin/fields/polymorphic/_form.html.erb +5 -32
  61. data/app/views/madmin/fields/polymorphic/_index.html.erb +3 -1
  62. data/app/views/madmin/fields/polymorphic/_show.html.erb +3 -14
  63. data/app/views/madmin/fields/rich_text/_form.html.erb +4 -0
  64. data/app/views/madmin/fields/rich_text/_index.html.erb +1 -0
  65. data/app/views/madmin/fields/rich_text/_show.html.erb +3 -0
  66. data/app/views/madmin/fields/string/_form.html.erb +2 -0
  67. data/app/views/madmin/fields/string/_index.html.erb +1 -0
  68. data/app/views/madmin/fields/string/_show.html.erb +1 -0
  69. data/app/views/madmin/fields/text/_form.html.erb +2 -4
  70. data/app/views/madmin/fields/text/_index.html.erb +1 -1
  71. data/app/views/madmin/fields/text/_show.html.erb +1 -8
  72. data/app/views/madmin/fields/time/_form.html.erb +2 -0
  73. data/app/views/madmin/fields/time/_index.html.erb +1 -0
  74. data/app/views/madmin/fields/time/_show.html.erb +1 -0
  75. data/lib/generators/madmin/install/install_generator.rb +31 -12
  76. data/lib/generators/madmin/install/templates/controller.rb.tt +22 -0
  77. data/lib/generators/madmin/resource/resource_generator.rb +83 -47
  78. data/lib/generators/madmin/resource/templates/controller.rb.tt +4 -0
  79. data/lib/generators/madmin/resource/templates/resource.rb.tt +11 -0
  80. data/lib/generators/madmin/views/edit_generator.rb +16 -0
  81. data/lib/generators/madmin/views/form_generator.rb +15 -0
  82. data/lib/generators/madmin/views/index_generator.rb +15 -0
  83. data/lib/generators/madmin/views/layout_generator.rb +21 -0
  84. data/lib/generators/madmin/views/navigation_generator.rb +15 -0
  85. data/lib/generators/madmin/views/new_generator.rb +16 -0
  86. data/lib/generators/madmin/views/show_generator.rb +15 -0
  87. data/lib/generators/madmin/views/views_generator.rb +8 -8
  88. data/lib/madmin.rb +34 -23
  89. data/lib/madmin/engine.rb +5 -2
  90. data/lib/madmin/field.rb +22 -46
  91. data/lib/madmin/fields/attachment.rb +6 -0
  92. data/lib/madmin/fields/attachments.rb +9 -0
  93. data/lib/madmin/fields/belongs_to.rb +18 -0
  94. data/lib/madmin/fields/boolean.rb +6 -0
  95. data/lib/madmin/fields/date.rb +6 -0
  96. data/lib/madmin/fields/date_time.rb +6 -0
  97. data/lib/madmin/fields/decimal.rb +6 -0
  98. data/lib/madmin/fields/enum.rb +9 -0
  99. data/lib/madmin/fields/float.rb +6 -0
  100. data/lib/madmin/fields/has_many.rb +18 -0
  101. data/lib/madmin/fields/has_one.rb +6 -0
  102. data/lib/madmin/fields/integer.rb +6 -0
  103. data/lib/madmin/fields/json.rb +6 -0
  104. data/lib/madmin/fields/polymorphic.rb +17 -0
  105. data/lib/madmin/fields/rich_text.rb +6 -0
  106. data/lib/madmin/fields/string.rb +6 -0
  107. data/lib/madmin/fields/text.rb +6 -0
  108. data/lib/madmin/fields/time.rb +6 -0
  109. data/lib/madmin/generator_helpers.rb +24 -0
  110. data/lib/madmin/namespace.rb +35 -0
  111. data/lib/madmin/resource.rb +176 -0
  112. data/lib/madmin/version.rb +1 -1
  113. data/lib/madmin/view_generator.rb +42 -0
  114. data/lib/tasks/madmin_tasks.rake +7 -0
  115. metadata +106 -111
  116. data/app/assets/config/madmin_manifest.js +0 -2
  117. data/app/assets/javascripts/madmin/application.js +0 -15
  118. data/app/assets/javascripts/madmin/dashboard.js +0 -2
  119. data/app/assets/javascripts/madmin/resources.js +0 -36
  120. data/app/assets/stylesheets/madmin/dashboard.css +0 -4
  121. data/app/assets/stylesheets/madmin/resources.css +0 -4
  122. data/app/controllers/madmin/application_controller.rb +0 -16
  123. data/app/controllers/madmin/resources_controller.rb +0 -97
  124. data/app/decorators/madmin/resource_decorator.rb +0 -16
  125. data/app/helpers/madmin/fields/polymorphic_helper.rb +0 -25
  126. data/app/jobs/madmin/application_job.rb +0 -4
  127. data/app/mailers/madmin/application_mailer.rb +0 -6
  128. data/app/models/madmin/application_record.rb +0 -5
  129. data/app/views/application/_navigation.html.erb +0 -17
  130. data/app/views/madmin/dashboard/index.html.erb +0 -6
  131. data/app/views/madmin/fields/check_box/_form.html.erb +0 -4
  132. data/app/views/madmin/fields/check_box/_index.html.erb +0 -1
  133. data/app/views/madmin/fields/check_box/_show.html.erb +0 -8
  134. data/app/views/madmin/fields/email/_form.html.erb +0 -4
  135. data/app/views/madmin/fields/email/_index.html.erb +0 -1
  136. data/app/views/madmin/fields/email/_show.html.erb +0 -8
  137. data/app/views/madmin/fields/number/_form.html.erb +0 -4
  138. data/app/views/madmin/fields/number/_index.html.erb +0 -1
  139. data/app/views/madmin/fields/number/_show.html.erb +0 -8
  140. data/app/views/madmin/fields/password/_form.html.erb +0 -4
  141. data/app/views/madmin/fields/password/_index.html.erb +0 -1
  142. data/app/views/madmin/fields/password/_show.html.erb +0 -8
  143. data/app/views/madmin/fields/select/_form.html.erb +0 -4
  144. data/app/views/madmin/fields/select/_index.html.erb +0 -1
  145. data/app/views/madmin/fields/select/_show.html.erb +0 -8
  146. data/app/views/madmin/fields/text_area/_form.html.erb +0 -4
  147. data/app/views/madmin/fields/text_area/_index.html.erb +0 -1
  148. data/app/views/madmin/fields/text_area/_show.html.erb +0 -8
  149. data/app/views/madmin/resources/_form.html.erb +0 -15
  150. data/app/views/madmin/resources/_scopes.html.erb +0 -10
  151. data/app/views/madmin/resources/edit.html.erb +0 -2
  152. data/app/views/madmin/resources/index.html.erb +0 -13
  153. data/app/views/madmin/resources/index/_content.html.erb +0 -33
  154. data/app/views/madmin/resources/new.html.erb +0 -2
  155. data/app/views/madmin/resources/show.html.erb +0 -10
  156. data/config/routes.rb +0 -11
  157. data/lib/generators/madmin/controller/USAGE +0 -8
  158. data/lib/generators/madmin/controller/controller_generator.rb +0 -10
  159. data/lib/generators/madmin/page/USAGE +0 -8
  160. data/lib/generators/madmin/page/page_generator.rb +0 -20
  161. data/lib/generators/madmin/page/templates/template.html.erb +0 -2
  162. data/lib/generators/madmin/page/templates/template.rb.erb +0 -10
  163. data/lib/generators/madmin/resource/templates/resource.rb.erb +0 -11
  164. data/lib/madmin/field/associatable.rb +0 -58
  165. data/lib/madmin/field/belongs_to.rb +0 -9
  166. data/lib/madmin/field/check_box.rb +0 -8
  167. data/lib/madmin/field/date_time.rb +0 -8
  168. data/lib/madmin/field/email.rb +0 -8
  169. data/lib/madmin/field/has_many.rb +0 -9
  170. data/lib/madmin/field/has_one.rb +0 -9
  171. data/lib/madmin/field/number.rb +0 -8
  172. data/lib/madmin/field/password.rb +0 -8
  173. data/lib/madmin/field/polymorphic.rb +0 -57
  174. data/lib/madmin/field/select.rb +0 -13
  175. data/lib/madmin/field/text.rb +0 -8
  176. data/lib/madmin/field/text_area.rb +0 -8
  177. data/lib/madmin/resourceable.rb +0 -72
  178. data/lib/madmin/resourceable/class_methods.rb +0 -152
  179. data/lib/madmin/resources.rb +0 -13
data/lib/madmin.rb CHANGED
@@ -1,30 +1,41 @@
1
1
  require "madmin/engine"
2
- require "madmin/resourceable"
3
- require "madmin/resources"
2
+
3
+ require "pagy"
4
4
 
5
5
  module Madmin
6
- class NoResourcesError < StandardError; end
7
- class ResourceNotFoundError < StandardError; end
8
- class WrongArgumentError < StandardError; end
9
- class UndefinedScopeError < StandardError; end
10
- class ScopeWithArgumentsError < StandardError; end
6
+ autoload :Field, "madmin/field"
7
+ autoload :GeneratorHelpers, "madmin/generator_helpers"
8
+ autoload :Resource, "madmin/resource"
9
+
10
+ module Fields
11
+ autoload :Boolean, "madmin/fields/boolean"
12
+ autoload :Integer, "madmin/fields/integer"
13
+ autoload :String, "madmin/fields/string"
14
+ autoload :Text, "madmin/fields/text"
15
+ autoload :Date, "madmin/fields/date"
16
+ autoload :DateTime, "madmin/fields/date_time"
17
+ autoload :Decimal, "madmin/fields/decimal"
18
+ autoload :Json, "madmin/fields/json"
19
+ autoload :Enum, "madmin/fields/enum"
20
+ autoload :Float, "madmin/fields/float"
21
+ autoload :Time, "madmin/fields/time"
22
+ autoload :BelongsTo, "madmin/fields/belongs_to"
23
+ autoload :Polymorphic, "madmin/fields/polymorphic"
24
+ autoload :HasMany, "madmin/fields/has_many"
25
+ autoload :HasOne, "madmin/fields/has_one"
26
+ autoload :RichText, "madmin/fields/rich_text"
27
+ autoload :Attachment, "madmin/fields/attachment"
28
+ autoload :Attachments, "madmin/fields/attachments"
29
+ end
30
+
31
+ mattr_accessor :resources, default: []
11
32
 
12
- autoload :Field, "madmin/field"
13
- autoload :Resourceable, "madmin/resourceable"
33
+ class << self
34
+ def resource_for(object)
35
+ Rails.application.eager_load!
14
36
 
15
- class Field
16
- autoload :Associatable, "madmin/field/associatable"
17
- autoload :BelongsTo, "madmin/field/belongs_to"
18
- autoload :CheckBox, "madmin/field/check_box"
19
- autoload :DateTime, "madmin/field/date_time"
20
- autoload :Email, "madmin/field/email"
21
- autoload :HasMany, "madmin/field/has_many"
22
- autoload :HasOne, "madmin/field/has_one"
23
- autoload :Number, "madmin/field/number"
24
- autoload :Password, "madmin/field/password"
25
- autoload :Polymorphic, "madmin/field/polymorphic"
26
- autoload :Select, "madmin/field/select"
27
- autoload :TextArea, "madmin/field/text_area"
28
- autoload :Text, "madmin/field/text"
37
+ klass_name = object.class.name
38
+ Madmin.resources.find { |r| r.model_name == klass_name }
39
+ end
29
40
  end
30
41
  end
data/lib/madmin/engine.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  module Madmin
2
2
  class Engine < ::Rails::Engine
3
- isolate_namespace Madmin
3
+ initializer "madmin.autoload", before: :set_autoload_paths do |app|
4
+ app.config.paths.add "app/madmin/resources", eager_load: true
5
+ app.config.paths.add "app/madmin/fields", eager_load: true
6
+ end
4
7
 
5
8
  config.to_prepare do
6
- Dir.glob("#{Rails.root}/app/madmin/**/*.rb").each { |r| require_dependency(r) }
9
+ Madmin.resources = []
7
10
  end
8
11
  end
9
12
  end
data/lib/madmin/field.rb CHANGED
@@ -1,64 +1,40 @@
1
1
  module Madmin
2
2
  class Field
3
- attr_reader :form
4
- attr_reader :index
5
- attr_reader :key
6
- attr_reader :label
7
- attr_reader :model
8
- attr_reader :options
9
- attr_reader :show
10
- attr_reader :type
3
+ attr_reader :attribute_name, :model, :options
11
4
 
12
- def initialize(args)
13
- @key = args[:key]
14
- @options = args
15
- @model = args[:model]
16
- @label = option_or_default(:label, label_from_attribute(key))
17
- @form = option_or_default(:form, false)
18
- @index = option_or_default(:index, false)
19
- @show = option_or_default(:show, true)
5
+ def self.field_type
6
+ to_s.split("::").last.underscore
20
7
  end
21
8
 
22
- ##
23
- # Returns an array of key(s) to use in strong
24
- # params for the resources controller.
25
- def strong_params_keys
26
- [key]
9
+ def initialize(attribute_name:, model:, **options)
10
+ @attribute_name = attribute_name
11
+ @model = model
12
+ @options = options
27
13
  end
28
14
 
29
- ##
30
- # Returns the partial path for the field type
31
- def to_partial_path
32
- "madmin/fields/#{self.class.to_s.split("Madmin::Field::").last.underscore}"
15
+ def value(record)
16
+ record.public_send(attribute_name)
33
17
  end
34
18
 
35
- ##
36
- # Returns the value for a given resource
37
- def value_for(resource)
38
- resource.send(key)
39
- end
19
+ def to_partial_path(name)
20
+ unless %w[index show form].include? name
21
+ raise ArgumentError, "`partial` must be 'index', 'show', or 'form'"
22
+ end
40
23
 
41
- private
24
+ "/madmin/fields/#{self.class.field_type}/#{name}"
25
+ end
42
26
 
43
- ##
44
- # This method takes an attribute and converts it
45
- # into a human readable format for form labels.
46
- def label_from_attribute(attribute)
47
- attribute.to_s.humanize
27
+ def to_param
28
+ attribute_name
48
29
  end
49
30
 
50
- ##
51
- # This method verifies options are present
52
- # (as a hash).
53
- def options?
54
- options.is_a?(Hash)
31
+ # Used for checking visibility of attribute on an view
32
+ def visible?(action, default: true)
33
+ options.fetch(action.to_sym, default)
55
34
  end
56
35
 
57
- ##
58
- # This method extracts the many option checks
59
- # performed when assigning field options.
60
- def option_or_default(key, default)
61
- options? ? options.fetch(key, default) : default
36
+ def required?
37
+ model.validators_on(attribute_name).any? { |v| v.is_a? ActiveModel::Validations::PresenceValidator }
62
38
  end
63
39
  end
64
40
  end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Attachment < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Madmin
2
+ module Fields
3
+ class Attachments < Field
4
+ def to_param
5
+ {attribute_name => []}
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Madmin
2
+ module Fields
3
+ class BelongsTo < Field
4
+ def options_for_select(record)
5
+ association = record.class.reflect_on_association(attribute_name)
6
+
7
+ klass = association.klass
8
+ klass.all.map do |r|
9
+ ["#{klass.name} ##{r.id}", r.id]
10
+ end
11
+ end
12
+
13
+ def to_param
14
+ "#{attribute_name}_id"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Boolean < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Date < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class DateTime < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Decimal < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Madmin
2
+ module Fields
3
+ class Enum < Field
4
+ def options_for_select(record)
5
+ model.defined_enums[attribute_name.to_s].keys
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Float < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module Madmin
2
+ module Fields
3
+ class HasMany < Field
4
+ def options_for_select(record)
5
+ association = record.class.reflect_on_association(attribute_name)
6
+
7
+ klass = association.klass
8
+ klass.all.map do |r|
9
+ ["#{klass.name} ##{r.id}", r.id]
10
+ end
11
+ end
12
+
13
+ def to_param
14
+ {"#{attribute_name.to_s.singularize}_ids".to_sym => []}
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class HasOne < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Integer < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Json < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module Madmin
2
+ module Fields
3
+ class Polymorphic < Field
4
+ def options_for_select(record)
5
+ if (collection = options[:collection])
6
+ collection.call
7
+ else
8
+ []
9
+ end
10
+ end
11
+
12
+ def to_param
13
+ {attribute_name => %i[type value]}
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class RichText < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class String < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Text < Field
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Time < Field
4
+ end
5
+ end
6
+ end
@@ -4,6 +4,30 @@ module Madmin
4
4
  Rails::Generators.invoke(generator, args, generator_options)
5
5
  end
6
6
 
7
+ def route_namespace_exists?
8
+ File.readlines(Rails.root.join("config/routes.rb")).grep(/namespace :madmin/).size > 0
9
+ end
10
+
11
+ # Method copied from Rails 6.1 master
12
+ def route(routing_code, namespace: nil, sentinel: nil, indentation: 2)
13
+ routing_code = Array(namespace).reverse.reduce(routing_code) { |code, ns|
14
+ "namespace :#{ns} do\n#{indent(code, 2)}\nend"
15
+ }
16
+
17
+ log :route, routing_code
18
+ sentinel ||= /\.routes\.draw do\s*\n/m
19
+
20
+ in_root do
21
+ inject_into_file "config/routes.rb", optimize_indentation(routing_code, indentation), after: sentinel, verbose: false, force: false
22
+ end
23
+ end
24
+
25
+ # Method copied from Rails 6.1 master
26
+ def optimize_indentation(value, amount = 0)
27
+ return "#{value}\n" unless value.is_a?(String)
28
+ "#{value.strip_heredoc.indent(amount).chomp}\n"
29
+ end
30
+
7
31
  private
8
32
 
9
33
  def generator_options
@@ -0,0 +1,35 @@
1
+ module Madmin
2
+ class Namespace
3
+ def initialize(namespace)
4
+ @namespace = namespace
5
+ end
6
+
7
+ def resources
8
+ @resources ||= routes.map(&:first).uniq.map { |path|
9
+ Resource.new(namespace, path)
10
+ }
11
+ end
12
+
13
+ def routes
14
+ @routes ||= all_routes.select { |controller, _action|
15
+ controller.starts_with?("#{namespace}/")
16
+ }.map { |controller, action|
17
+ [controller.gsub(/^#{namespace}\//, ""), action]
18
+ }
19
+ end
20
+
21
+ def resources_with_index_route
22
+ routes.select { |_resource, route| route == "index" }.map(&:first).uniq
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :namespace
28
+
29
+ def all_routes
30
+ Rails.application.routes.routes.map do |route|
31
+ route.defaults.values_at(:controller, :action).map(&:to_s)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,176 @@
1
+ module Madmin
2
+ class Resource
3
+ class_attribute :attributes, default: []
4
+ class_attribute :scopes, default: []
5
+
6
+ class << self
7
+ def inherited(base)
8
+ # Remove any old references
9
+ Madmin.resources.delete(base)
10
+ Madmin.resources << base
11
+
12
+ base.attributes = attributes.dup
13
+ base.scopes = scopes.dup
14
+ super
15
+ end
16
+
17
+ def model
18
+ model_name.constantize
19
+ end
20
+
21
+ def model_name
22
+ to_s.chomp("Resource").classify
23
+ end
24
+
25
+ def scope(name)
26
+ scopes << name
27
+ end
28
+
29
+ def attribute(name, type = nil, **options)
30
+ attributes << {
31
+ name: name,
32
+ field: field_for_type(name, type).new(**options.merge(attribute_name: name, model: model))
33
+ }
34
+ end
35
+
36
+ def friendly_name
37
+ model_name.gsub("::", " / ")
38
+ end
39
+
40
+ def index_path(options = {})
41
+ path = "/madmin/#{model.model_name.collection}"
42
+ path += "?#{options.to_param}" if options.any?
43
+ path
44
+ end
45
+
46
+ def new_path
47
+ "/madmin/#{model.model_name.collection}/new"
48
+ end
49
+
50
+ def show_path(record)
51
+ "/madmin/#{model.model_name.collection}/#{record.id}"
52
+ end
53
+
54
+ def edit_path(record)
55
+ "/madmin/#{model.model_name.collection}/#{record.id}/edit"
56
+ end
57
+
58
+ def param_key
59
+ model.model_name.param_key
60
+ end
61
+
62
+ def permitted_params
63
+ attributes.map { |a| a[:field].to_param }
64
+ end
65
+
66
+ def display_name(record)
67
+ "#{record.class} ##{record.id}"
68
+ end
69
+
70
+ private
71
+
72
+ def field_for_type(name, type)
73
+ type ||= infer_type(name)
74
+
75
+ {
76
+ binary: Fields::String,
77
+ blob: Fields::Text,
78
+ boolean: Fields::Boolean,
79
+ date: Fields::Date,
80
+ datetime: Fields::DateTime,
81
+ decimal: Fields::Decimal,
82
+ enum: Fields::Enum,
83
+ float: Fields::Float,
84
+ hstore: Fields::Json,
85
+ integer: Fields::Integer,
86
+ json: Fields::Json,
87
+ jsonb: Fields::Json,
88
+ primary_key: Fields::String,
89
+ string: Fields::String,
90
+ text: Fields::Text,
91
+ time: Fields::Time,
92
+ timestamp: Fields::Time,
93
+
94
+ # Postgres specific types
95
+ bit: Fields::String,
96
+ bit_varying: Fields::String,
97
+ box: Fields::String,
98
+ cidr: Fields::String,
99
+ circle: Fields::String,
100
+ citext: Fields::Text,
101
+ daterange: Fields::String,
102
+ inet: Fields::String,
103
+ int4range: Fields::String,
104
+ int8range: Fields::String,
105
+ interval: Fields::String,
106
+ line: Fields::String,
107
+ lseg: Fields::String,
108
+ ltree: Fields::String,
109
+ macaddr: Fields::String,
110
+ money: Fields::String,
111
+ numrange: Fields::String,
112
+ oid: Fields::String,
113
+ path: Fields::String,
114
+ point: Fields::String,
115
+ polygon: Fields::String,
116
+ tsrange: Fields::String,
117
+ tstzrange: Fields::String,
118
+ tsvector: Fields::String,
119
+ uuid: Fields::String,
120
+ xml: Fields::Text,
121
+
122
+ # Associations
123
+ attachment: Fields::Attachment,
124
+ attachments: Fields::Attachments,
125
+ belongs_to: Fields::BelongsTo,
126
+ polymorphic: Fields::Polymorphic,
127
+ has_many: Fields::HasMany,
128
+ has_one: Fields::HasOne,
129
+ rich_text: Fields::RichText
130
+ }.fetch(type)
131
+ rescue
132
+ raise ArgumentError, <<~MESSAGE
133
+ Couldn't find attribute or association '#{name}' with type '#{type}' on #{model} model
134
+
135
+ To fix this, either:
136
+
137
+ 1. Remove 'attribute #{name}' from app/madmin/resources/#{model.to_s.underscore}_resource.rb
138
+ 2. Or add the missing attribute or association to the #{model} model
139
+ MESSAGE
140
+ end
141
+
142
+ def infer_type(name)
143
+ name_string = name.to_s
144
+
145
+ if model.attribute_types.include?(name_string)
146
+ column_type = model.attribute_types[name_string]
147
+ if column_type.is_a? ActiveRecord::Enum::EnumType
148
+ :enum
149
+ else
150
+ column_type.type || :string
151
+ end
152
+ elsif (association = model.reflect_on_association(name))
153
+ type_for_association(association)
154
+ elsif model.reflect_on_association(:"rich_text_#{name_string}")
155
+ :rich_text
156
+ elsif model.reflect_on_association(:"#{name_string}_attachment")
157
+ :attachment
158
+ elsif model.reflect_on_association(:"#{name_string}_attachments")
159
+ :attachments
160
+ end
161
+ end
162
+
163
+ def type_for_association(association)
164
+ if association.has_one?
165
+ :has_one
166
+ elsif association.collection?
167
+ :has_many
168
+ elsif association.polymorphic?
169
+ :polymorphic
170
+ else
171
+ :belongs_to
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end