madmin 1.0.0.beta1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/app/controllers/madmin/resource_controller.rb +1 -1
  4. data/app/views/madmin/application/_javascript.html.erb +40 -5
  5. data/app/views/madmin/fields/belongs_to/_index.html.erb +3 -2
  6. data/app/views/madmin/fields/belongs_to/_show.html.erb +3 -2
  7. data/app/views/madmin/fields/enum/_form.html.erb +1 -2
  8. data/app/views/madmin/fields/nested_has_many/_fields.html.erb +18 -0
  9. data/app/views/madmin/fields/nested_has_many/_form.html.erb +30 -0
  10. data/app/views/madmin/fields/nested_has_many/_index.html.erb +1 -0
  11. data/app/views/madmin/fields/nested_has_many/_show.html.erb +5 -0
  12. data/app/views/madmin/fields/password/_form.html.erb +2 -0
  13. data/app/views/madmin/fields/password/_index.html.erb +1 -0
  14. data/app/views/madmin/fields/password/_show.html.erb +1 -0
  15. data/lib/generators/madmin/install/install_generator.rb +2 -1
  16. data/lib/generators/madmin/resource/resource_generator.rb +14 -3
  17. data/lib/generators/madmin/resource/templates/resource.rb.tt +5 -0
  18. data/lib/generators/madmin/views/edit_generator.rb +16 -0
  19. data/lib/generators/madmin/views/form_generator.rb +15 -0
  20. data/lib/generators/madmin/views/index_generator.rb +15 -0
  21. data/lib/generators/madmin/views/javascript_generator.rb +15 -0
  22. data/lib/generators/madmin/views/layout_generator.rb +21 -0
  23. data/lib/generators/madmin/views/navigation_generator.rb +15 -0
  24. data/lib/generators/madmin/views/new_generator.rb +16 -0
  25. data/lib/generators/madmin/views/show_generator.rb +15 -0
  26. data/lib/generators/madmin/views/views_generator.rb +16 -0
  27. data/lib/madmin.rb +11 -9
  28. data/lib/madmin/fields/belongs_to.rb +3 -2
  29. data/lib/madmin/fields/enum.rb +3 -0
  30. data/lib/madmin/fields/has_many.rb +3 -2
  31. data/lib/madmin/fields/nested_has_many.rb +40 -0
  32. data/lib/madmin/fields/password.rb +6 -0
  33. data/lib/madmin/namespace.rb +35 -0
  34. data/lib/madmin/resource.rb +37 -8
  35. data/lib/madmin/version.rb +1 -1
  36. data/lib/madmin/view_generator.rb +42 -0
  37. metadata +24 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25bf67586811ced1dc2612fe0382c517f656895261e5b24b6c90c0cd6d9f21f6
4
- data.tar.gz: a41bbd0a7be385b1aad8fef678ee61a56f90964fc1dc749cf728d3cc52739d54
3
+ metadata.gz: 223251f99312a051f3fd6a6d926c6583347e2c78b255343bc66b3fb063ea8a54
4
+ data.tar.gz: cfab141916431f52d137bd7704c33cc4cd3b1781735062e2a9d173f9d9d7bee4
5
5
  SHA512:
6
- metadata.gz: 99bdd270af3258778d08d0b7ec69f1cbb3406d7f14fe29a10892afaf09f3857c0fa114287f6aaa4f253aefc40f2bad5d626109caa0340638ed091e8c9f7fdeef
7
- data.tar.gz: 21a2fb128bcc6e2e89e443694d4bceac0e9b94838da425333845539eef99ae1696bdf1d385ed530b1262ccdaa6746d10ab864424796be193192165996d4352f2
6
+ metadata.gz: 7b1480b7df30ec0bba0f1937a7336bb8532f51cd8dc9c8de78fd73b686c132dd14083eec88a303938bfb4fdfa50827db570a3d899e40952799869dfe4795e756
7
+ data.tar.gz: e362a3d85149b1f1074ea21eaf6a6659e6c8f002a929f668a21583a286fc1824a2f3ede02997eafe75feaa20e0172345c54210180b7c537b44d0de4bca1eb0b0
data/README.md CHANGED
@@ -7,9 +7,12 @@
7
7
  Why another Ruby on Rails admin? We wanted an admin that was:
8
8
 
9
9
  * Familiar and customizable like Rails scaffolds (less DSL)
10
- * Supports all the Rails features out of the box (ActionText, ActionMailbox, etc)
10
+ * Supports all the Rails features out of the box (ActionText, ActionMailbox, has_secure_password, etc)
11
11
  * Stimulus / Turbolinks / Hotwire ready
12
12
 
13
+ ![Madmin Screenshot](docs/images/screenshot.png)
14
+ _We're still working on the design!_
15
+
13
16
  ## Installation
14
17
  Add `madmin` to your application's Gemfile:
15
18
 
@@ -37,6 +40,50 @@ To generate a resource for a model, you can run:
37
40
  rails g madmin:resource ActionText::RichText
38
41
  ```
39
42
 
43
+ ## Configuring Views
44
+
45
+ The views packaged within the gem are a great starting point, but inevitably people will need to be able to customize those views.
46
+
47
+ You can use the included generator to create the appropriate view files, which can then be customized.
48
+
49
+ For example, running the following will copy over all of the views into your application that will be used for every resource:
50
+ ```bash
51
+ rails generate madmin:views
52
+ ```
53
+
54
+ The view files that are copied over in this case includes all of the standard Rails action views (index, new, edit, show, and _form), as well as:
55
+ * `application.html.erb` (layout file)
56
+ * `_javascript.html.erb` (default JavaScript setup)
57
+ * `_navigation.html.erb` (renders the navigation/sidebar menu)
58
+
59
+ As with the other views, you can specifically run the views generator for only the navigation or application layout views:
60
+ ```bash
61
+ rails g madmin:views:navigation
62
+ # -> app/views/madmin/_navigation.html.erb
63
+
64
+ rails g madmin:views:layout # Note the layout generator includes the layout, javascript, and navigation files.
65
+ # -> app/views/madmin/application.html.erb
66
+ # -> app/views/madmin/_javascript.html.erb
67
+ # -> app/views/madmin/_navigation.html.erb
68
+ ```
69
+
70
+ If you only need to customize specific views, you can restrict which views are copied by the generator:
71
+ ```bash
72
+ rails g madmin:views:index
73
+ # -> app/views/madmin/application/index.html.erb
74
+ ```
75
+
76
+ You can also scope the copied view(s) to a specific Resource/Model:
77
+ ```bash
78
+ rails generate madmin:views:index Book
79
+ # -> app/views/madmin/books/index.html.erb
80
+ ```
81
+
82
+ ## Authentication
83
+
84
+ You can use a couple of strategies to authenticate users who are trying to
85
+ access your madmin panel: [Authentication Docs](docs/authentication.md)
86
+
40
87
  ## 🙏 Contributing
41
88
 
42
89
  This project uses Standard for formatting Ruby code. Please make sure to run standardrb before submitting pull requests.
@@ -41,7 +41,7 @@ module Madmin
41
41
  private
42
42
 
43
43
  def set_record
44
- @record = resource.model.find(params[:id])
44
+ @record = resource.model_find(params[:id])
45
45
  end
46
46
 
47
47
  def resource
@@ -1,9 +1,9 @@
1
- <%= stylesheet_link_tag "https://cdn.skypack.dev/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
2
- <%= stylesheet_link_tag "https://cdn.skypack.dev/slim-select/dist/slimselect.min.css", "data-turbo-track": "reload" %>
3
- <%= stylesheet_link_tag "https://cdn.skypack.dev/trix/dist/trix.css", "data-turbo-track": "reload" %>
1
+ <%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
2
+ <%= stylesheet_link_tag "https://unpkg.com/trix/dist/trix.css", "data-turbo-track": "reload" %>
3
+ <%= stylesheet_link_tag "https://unpkg.com/slim-select@1.27.0/dist/slimselect.min.css", "data-turbo-track": "reload" %>
4
4
 
5
5
  <script type="module">
6
- import { Application } from 'https://cdn.skypack.dev/stimulus'
6
+ import { Application, Controller } from 'https://cdn.skypack.dev/stimulus'
7
7
  const application = Application.start()
8
8
 
9
9
  import stimulusFlatpickr from 'https://cdn.skypack.dev/stimulus-flatpickr'
@@ -17,8 +17,43 @@
17
17
  import 'https://cdn.skypack.dev/trix'
18
18
  import 'https://cdn.skypack.dev/@rails/actiontext@<%= npm_rails_version %>'
19
19
 
20
- Rails.start()
20
+ if (!window._rails_loaded) { Rails.start() }
21
21
  ActiveStorage.start()
22
22
 
23
23
  import * as Turbo from "https://cdn.skypack.dev/@hotwired/turbo"
24
+
25
+ (() => {
26
+ application.register('nested-form', class extends Controller {
27
+ static get targets() {
28
+ return [ "links", "template" ]
29
+ }
30
+
31
+ connect() {
32
+ this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
33
+ }
34
+
35
+ add_association(event) {
36
+ event.preventDefault()
37
+
38
+ var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
39
+ this.linksTarget.insertAdjacentHTML('beforebegin', content)
40
+ }
41
+
42
+ remove_association(event) {
43
+ event.preventDefault()
44
+
45
+ let wrapper = event.target.closest("." + this.wrapperClass)
46
+
47
+ // New records are simply removed from the page
48
+ if (wrapper.dataset.newRecord == "true") {
49
+ wrapper.remove()
50
+
51
+ // Existing records are hidden and flagged for deletion
52
+ } else {
53
+ wrapper.querySelector("input[name*='_destroy']").value = 1
54
+ wrapper.style.display = 'none'
55
+ }
56
+ }
57
+ })
58
+ })()
24
59
  </script>
@@ -1,2 +1,3 @@
1
- <% object = field.value(record) %>
2
- <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
1
+ <% if (object = field.value(record)) %>
2
+ <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
3
+ <% end %>
@@ -1,2 +1,3 @@
1
- <% object = field.value(record) %>
2
- <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
1
+ <% if (object = field.value(record)) %>
2
+ <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
3
+ <% end %>
@@ -1,3 +1,2 @@
1
- ENUM
2
1
  <%= form.label field.attribute_name, class: "inline-block w-32 flex-shrink-0" %>
3
- <%= field.value(record) %>
2
+ <%= form.select field.attribute_name, field.options_for_select(record), { prompt: true }, { class: "form-select" } %>
@@ -0,0 +1,18 @@
1
+ <%= content_tag :div, class: "nested-fields bg-gray-100 rounded-t-xl p-5", data: { new_record: f.object.new_record? } do %>
2
+ <% field.nested_attributes.each do |nested_attribute| %>
3
+ <% next if nested_attribute[:field].nil? %>
4
+ <% next unless nested_attribute[:field].visible?(action_name) %>
5
+ <% next unless nested_attribute[:field].visible?(:form) %>
6
+
7
+ <% nested_field = nested_attribute[:field] %>
8
+
9
+ <div class="mb-4 flex">
10
+ <%= render partial: nested_field.to_partial_path("form"), locals: { field: nested_field, record: f.object, form: f, resource: field.resource } %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <small><%= link_to "Remove", "#", data: { action: "click->nested-form#remove_association" } %></small>
15
+
16
+ <%= f.hidden_field :_destroy %>
17
+
18
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <%= form.label field.attribute_name, class: "inline-block w-32 flex-shrink-0" %>
2
+
3
+ <div class="container space-y-8" data-controller="nested-form">
4
+ <template data-target="nested-form.template">
5
+
6
+ <%= form.fields_for field.attribute_name, field.to_model.new, child_index: 'NEW_RECORD' do |nested_form| %>
7
+ <%= render(
8
+ partial: field.to_partial_path('fields'),
9
+ locals: {
10
+ f: nested_form,
11
+ field: field
12
+ }
13
+ ) %>
14
+ <% end %>
15
+ </template>
16
+
17
+ <%= form.fields_for field.attribute_name do |nested_form| %>
18
+ <%= render(
19
+ partial: field.to_partial_path('fields'),
20
+ locals: {
21
+ f: nested_form,
22
+ field: field
23
+ }
24
+ ) %>
25
+ <% end %>
26
+
27
+ <%= content_tag :div, class: '', data: { target:"nested-form.links" } do %>
28
+ <%= link_to "+ Add new", "#", data: { action: "click->nested-form#add_association" } %>
29
+ <% end %>
30
+ </div>
@@ -0,0 +1 @@
1
+ <%= pluralize field.value(record).count, field.attribute_name.to_s %>
@@ -0,0 +1,5 @@
1
+ <% field.value(record).each do |object| %>
2
+ <div>
3
+ <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
4
+ </div>
5
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <%= form.label field.attribute_name, class: "inline-block w-32 flex-shrink-0" %>
2
+ <%= form.password_field field.attribute_name, class: "form-input" %>
@@ -0,0 +1 @@
1
+ <%= field.value(record) %>
@@ -0,0 +1 @@
1
+ <%= field.value(record) %>
@@ -35,9 +35,10 @@ module Madmin
35
35
 
36
36
  private
37
37
 
38
+ # Skip Abstract classes, ActiveRecord::Base, and auto-generated HABTM models
38
39
  def generateable_models
39
40
  active_record_models.reject do |model|
40
- model.abstract_class? || model == ActiveRecord::Base
41
+ model.abstract_class? || model == ActiveRecord::Base || model.name.start_with?("HABTM_")
41
42
  end
42
43
  end
43
44
 
@@ -46,6 +46,10 @@ module Madmin
46
46
  def virtual_attributes
47
47
  virtual = []
48
48
 
49
+ # has_secure_password columns
50
+ password_attributes = model.attribute_types.keys.select { |k| k.ends_with?("_digest") }.map { |k| k.delete_suffix("_digest") }
51
+ virtual += password_attributes.map { |attr| [attr, "#{attr}_confirmation"] }.flatten
52
+
49
53
  # Add virtual attributes for ActionText and ActiveStorage
50
54
  model.reflections.each do |name, association|
51
55
  if name.starts_with?("rich_text")
@@ -63,6 +67,9 @@ module Madmin
63
67
  def redundant_attributes
64
68
  redundant = []
65
69
 
70
+ # has_secure_password columns
71
+ redundant += model.attribute_types.keys.select { |k| k.ends_with?("_digest") }
72
+
66
73
  model.reflections.each do |name, association|
67
74
  if association.has_one?
68
75
  next
@@ -98,13 +105,17 @@ module Madmin
98
105
  if %w[id created_at updated_at].include?(name)
99
106
  {form: false}
100
107
 
101
- # Attributes without a database column
102
- elsif !model.column_names.include?(name)
103
- {index: false}
108
+ # has_secure_passwords should only show on forms
109
+ elsif name.ends_with?("_confirmation") || virtual_attributes.include?("#{name}_confirmation")
110
+ {index: false, show: false}
104
111
 
105
112
  # Counter cache columns are typically not editable
106
113
  elsif name.ends_with?("_count")
107
114
  {form: false}
115
+
116
+ # Attributes without a database column
117
+ elsif !model.column_names.include?(name)
118
+ {index: false}
108
119
  end
109
120
  end
110
121
  end
@@ -8,4 +8,9 @@ class <%= class_name %>Resource < Madmin::Resource
8
8
  <% associations.each do |association_name| -%>
9
9
  attribute :<%= association_name %>
10
10
  <% end -%>
11
+
12
+ # Uncomment this to customize the display name of records in the admin area.
13
+ # def self.display_name(record)
14
+ # record.name
15
+ # end
11
16
  end
@@ -0,0 +1,16 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class EditGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_edit
10
+ copy_resource_template("edit")
11
+ copy_resource_template("_form")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class FormGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_form
10
+ copy_resource_template("_form")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class IndexGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_template
10
+ copy_resource_template("index")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class JavascriptGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_navigation
10
+ copy_resource_template("_javascript")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class LayoutGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_template
10
+ copy_file(
11
+ "../../layouts/madmin/application.html.erb",
12
+ "app/views/layouts/madmin/application.html.erb"
13
+ )
14
+
15
+ call_generator("madmin:views:navigation")
16
+ copy_resource_template("_javascript")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class NavigationGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_navigation
10
+ copy_resource_template("_navigation")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class NewGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_new
10
+ copy_resource_template("new")
11
+ copy_resource_template("_form")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ module Views
6
+ class ShowGenerator < Madmin::ViewGenerator
7
+ source_root template_source_path
8
+
9
+ def copy_template
10
+ copy_resource_template("show")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require "madmin/view_generator"
2
+
3
+ module Madmin
4
+ module Generators
5
+ class ViewsGenerator < Madmin::ViewGenerator
6
+ def copy_templates
7
+ # Some generators duplicate templates, so not everything is present here
8
+ call_generator("madmin:views:edit", resource_path, "--namespace", namespace)
9
+ call_generator("madmin:views:index", resource_path, "--namespace", namespace)
10
+ call_generator("madmin:views:layout", resource_path, "--namespace", namespace)
11
+ call_generator("madmin:views:new", resource_path, "--namespace", namespace)
12
+ call_generator("madmin:views:show", resource_path, "--namespace", namespace)
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/madmin.rb CHANGED
@@ -8,24 +8,26 @@ module Madmin
8
8
  autoload :Resource, "madmin/resource"
9
9
 
10
10
  module Fields
11
+ autoload :Attachment, "madmin/fields/attachment"
12
+ autoload :Attachments, "madmin/fields/attachments"
13
+ autoload :BelongsTo, "madmin/fields/belongs_to"
11
14
  autoload :Boolean, "madmin/fields/boolean"
12
- autoload :Integer, "madmin/fields/integer"
13
- autoload :String, "madmin/fields/string"
14
- autoload :Text, "madmin/fields/text"
15
15
  autoload :Date, "madmin/fields/date"
16
16
  autoload :DateTime, "madmin/fields/date_time"
17
17
  autoload :Decimal, "madmin/fields/decimal"
18
- autoload :Json, "madmin/fields/json"
19
18
  autoload :Enum, "madmin/fields/enum"
20
19
  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
20
  autoload :HasMany, "madmin/fields/has_many"
25
21
  autoload :HasOne, "madmin/fields/has_one"
22
+ autoload :Integer, "madmin/fields/integer"
23
+ autoload :Json, "madmin/fields/json"
24
+ autoload :NestedHasMany, "madmin/fields/nested_has_many"
25
+ autoload :Password, "madmin/fields/password"
26
+ autoload :Polymorphic, "madmin/fields/polymorphic"
26
27
  autoload :RichText, "madmin/fields/rich_text"
27
- autoload :Attachment, "madmin/fields/attachment"
28
- autoload :Attachments, "madmin/fields/attachments"
28
+ autoload :String, "madmin/fields/string"
29
+ autoload :Text, "madmin/fields/text"
30
+ autoload :Time, "madmin/fields/time"
29
31
  end
30
32
 
31
33
  mattr_accessor :resources, default: []
@@ -3,10 +3,11 @@ module Madmin
3
3
  class BelongsTo < Field
4
4
  def options_for_select(record)
5
5
  association = record.class.reflect_on_association(attribute_name)
6
-
7
6
  klass = association.klass
7
+ resource = nil
8
8
  klass.all.map do |r|
9
- ["#{klass.name} ##{r.id}", r.id]
9
+ resource ||= Madmin.resource_for(r)
10
+ [resource.display_name(r), r.id]
10
11
  end
11
12
  end
12
13
 
@@ -1,6 +1,9 @@
1
1
  module Madmin
2
2
  module Fields
3
3
  class Enum < Field
4
+ def options_for_select(record)
5
+ model.defined_enums[attribute_name.to_s].keys
6
+ end
4
7
  end
5
8
  end
6
9
  end
@@ -3,10 +3,11 @@ module Madmin
3
3
  class HasMany < Field
4
4
  def options_for_select(record)
5
5
  association = record.class.reflect_on_association(attribute_name)
6
-
7
6
  klass = association.klass
7
+ resource = nil
8
8
  klass.all.map do |r|
9
- ["#{klass.name} ##{r.id}", r.id]
9
+ resource ||= Madmin.resource_for(r)
10
+ [resource.display_name(r), r.id]
10
11
  end
11
12
  end
12
13
 
@@ -0,0 +1,40 @@
1
+ module Madmin
2
+ module Fields
3
+ class NestedHasMany < Field
4
+ DEFAULT_ATTRIBUTES = %w[_destroy id].freeze
5
+ def nested_attributes
6
+ resource.attributes.reject { |i| skipped_fields.include?(i[:name]) }
7
+ end
8
+
9
+ def resource
10
+ "#{to_model.name}Resource".constantize
11
+ end
12
+
13
+ def to_param
14
+ {"#{attribute_name}_attributes": permitted_fields}
15
+ end
16
+
17
+ def to_partial_path(name)
18
+ unless %w[index show form fields].include? name
19
+ raise ArgumentError, "`partial` must be 'index', 'show', 'form' or 'fields'"
20
+ end
21
+
22
+ "/madmin/fields/#{self.class.field_type}/#{name}"
23
+ end
24
+
25
+ def to_model
26
+ attribute_name.to_s.singularize.classify.constantize
27
+ end
28
+
29
+ private
30
+
31
+ def permitted_fields
32
+ (resource.permitted_params - skipped_fields + DEFAULT_ATTRIBUTES).uniq
33
+ end
34
+
35
+ def skipped_fields
36
+ options[:skip] || []
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,6 @@
1
+ module Madmin
2
+ module Fields
3
+ class Password < Field
4
+ end
5
+ end
6
+ end
@@ -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
@@ -18,6 +18,10 @@ module Madmin
18
18
  model_name.constantize
19
19
  end
20
20
 
21
+ def model_find(id)
22
+ friendly_model? ? model.friendly.find(id) : model.find(id)
23
+ end
24
+
21
25
  def model_name
22
26
  to_s.chomp("Resource").classify
23
27
  end
@@ -38,21 +42,27 @@ module Madmin
38
42
  end
39
43
 
40
44
  def index_path(options = {})
41
- path = "/madmin/#{model.model_name.collection}"
42
- path += "?#{options.to_param}" if options.any?
43
- path
45
+ route_name = "madmin_#{model.table_name}_path"
46
+
47
+ url_helpers.send(route_name, options)
44
48
  end
45
49
 
46
50
  def new_path
47
- "/madmin/#{model.model_name.collection}/new"
51
+ route_name = "new_madmin_#{model.model_name.singular}_path"
52
+
53
+ url_helpers.send(route_name)
48
54
  end
49
55
 
50
56
  def show_path(record)
51
- "/madmin/#{model.model_name.collection}/#{record.id}"
57
+ route_name = "madmin_#{model.model_name.singular}_path"
58
+
59
+ url_helpers.send(route_name, record.to_param)
52
60
  end
53
61
 
54
62
  def edit_path(record)
55
- "/madmin/#{model.model_name.collection}/#{record.id}/edit"
63
+ route_name = "edit_madmin_#{model.model_name.singular}_path"
64
+
65
+ url_helpers.send(route_name, record.to_param)
56
66
  end
57
67
 
58
68
  def param_key
@@ -67,6 +77,10 @@ module Madmin
67
77
  "#{record.class} ##{record.id}"
68
78
  end
69
79
 
80
+ def friendly_model?
81
+ model.respond_to? :friendly
82
+ end
83
+
70
84
  private
71
85
 
72
86
  def field_for_type(name, type)
@@ -90,6 +104,7 @@ module Madmin
90
104
  text: Fields::Text,
91
105
  time: Fields::Time,
92
106
  timestamp: Fields::Time,
107
+ password: Fields::Password,
93
108
 
94
109
  # Postgres specific types
95
110
  bit: Fields::String,
@@ -126,7 +141,8 @@ module Madmin
126
141
  polymorphic: Fields::Polymorphic,
127
142
  has_many: Fields::HasMany,
128
143
  has_one: Fields::HasOne,
129
- rich_text: Fields::RichText
144
+ rich_text: Fields::RichText,
145
+ nested_has_many: Fields::NestedHasMany
130
146
  }.fetch(type)
131
147
  rescue
132
148
  raise ArgumentError, <<~MESSAGE
@@ -143,7 +159,12 @@ module Madmin
143
159
  name_string = name.to_s
144
160
 
145
161
  if model.attribute_types.include?(name_string)
146
- model.attribute_types[name_string].type || :string
162
+ column_type = model.attribute_types[name_string]
163
+ if column_type.is_a? ActiveRecord::Enum::EnumType
164
+ :enum
165
+ else
166
+ column_type.type || :string
167
+ end
147
168
  elsif (association = model.reflect_on_association(name))
148
169
  type_for_association(association)
149
170
  elsif model.reflect_on_association(:"rich_text_#{name_string}")
@@ -152,6 +173,10 @@ module Madmin
152
173
  :attachment
153
174
  elsif model.reflect_on_association(:"#{name_string}_attachments")
154
175
  :attachments
176
+
177
+ # has_secure_password
178
+ elsif model.attribute_types.include?("#{name_string}_digest") || name_string.ends_with?("_confirmation")
179
+ :password
155
180
  end
156
181
  end
157
182
 
@@ -166,6 +191,10 @@ module Madmin
166
191
  :belongs_to
167
192
  end
168
193
  end
194
+
195
+ def url_helpers
196
+ @url_helpers ||= Rails.application.routes.url_helpers
197
+ end
169
198
  end
170
199
  end
171
200
  end
@@ -1,3 +1,3 @@
1
1
  module Madmin
2
- VERSION = "1.0.0.beta1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,42 @@
1
+ require "rails/generators/base"
2
+ require "madmin/generator_helpers"
3
+ require "madmin/namespace"
4
+
5
+ module Madmin
6
+ class ViewGenerator < Rails::Generators::Base
7
+ include Madmin::GeneratorHelpers
8
+ class_option :namespace, type: :string, default: "madmin"
9
+
10
+ def self.template_source_path
11
+ File.expand_path(
12
+ "../../../app/views/madmin/application",
13
+ __FILE__
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ def namespace
20
+ options[:namespace]
21
+ end
22
+
23
+ def copy_resource_template(template_name)
24
+ template_file = "#{template_name}.html.erb"
25
+
26
+ copy_file(
27
+ template_file,
28
+ "app/views/#{namespace}/#{resource_path}/#{template_file}"
29
+ )
30
+ end
31
+
32
+ def resource_path
33
+ args.first.try(:underscore).try(:pluralize) || BaseResourcePath.new
34
+ end
35
+
36
+ class BaseResourcePath
37
+ def to_s
38
+ "application"
39
+ end
40
+ end
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: madmin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Oliver
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-05 00:00:00.000000000 Z
12
+ date: 2021-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -111,6 +111,13 @@ files:
111
111
  - app/views/madmin/fields/json/_form.html.erb
112
112
  - app/views/madmin/fields/json/_index.html.erb
113
113
  - app/views/madmin/fields/json/_show.html.erb
114
+ - app/views/madmin/fields/nested_has_many/_fields.html.erb
115
+ - app/views/madmin/fields/nested_has_many/_form.html.erb
116
+ - app/views/madmin/fields/nested_has_many/_index.html.erb
117
+ - app/views/madmin/fields/nested_has_many/_show.html.erb
118
+ - app/views/madmin/fields/password/_form.html.erb
119
+ - app/views/madmin/fields/password/_index.html.erb
120
+ - app/views/madmin/fields/password/_show.html.erb
114
121
  - app/views/madmin/fields/polymorphic/_form.html.erb
115
122
  - app/views/madmin/fields/polymorphic/_index.html.erb
116
123
  - app/views/madmin/fields/polymorphic/_show.html.erb
@@ -131,6 +138,15 @@ files:
131
138
  - lib/generators/madmin/resource/resource_generator.rb
132
139
  - lib/generators/madmin/resource/templates/controller.rb.tt
133
140
  - lib/generators/madmin/resource/templates/resource.rb.tt
141
+ - lib/generators/madmin/views/edit_generator.rb
142
+ - lib/generators/madmin/views/form_generator.rb
143
+ - lib/generators/madmin/views/index_generator.rb
144
+ - lib/generators/madmin/views/javascript_generator.rb
145
+ - lib/generators/madmin/views/layout_generator.rb
146
+ - lib/generators/madmin/views/navigation_generator.rb
147
+ - lib/generators/madmin/views/new_generator.rb
148
+ - lib/generators/madmin/views/show_generator.rb
149
+ - lib/generators/madmin/views/views_generator.rb
134
150
  - lib/madmin.rb
135
151
  - lib/madmin/engine.rb
136
152
  - lib/madmin/field.rb
@@ -147,14 +163,18 @@ files:
147
163
  - lib/madmin/fields/has_one.rb
148
164
  - lib/madmin/fields/integer.rb
149
165
  - lib/madmin/fields/json.rb
166
+ - lib/madmin/fields/nested_has_many.rb
167
+ - lib/madmin/fields/password.rb
150
168
  - lib/madmin/fields/polymorphic.rb
151
169
  - lib/madmin/fields/rich_text.rb
152
170
  - lib/madmin/fields/string.rb
153
171
  - lib/madmin/fields/text.rb
154
172
  - lib/madmin/fields/time.rb
155
173
  - lib/madmin/generator_helpers.rb
174
+ - lib/madmin/namespace.rb
156
175
  - lib/madmin/resource.rb
157
176
  - lib/madmin/version.rb
177
+ - lib/madmin/view_generator.rb
158
178
  - lib/tasks/madmin_tasks.rake
159
179
  homepage: https://github.com/excid3/madmin
160
180
  licenses:
@@ -171,9 +191,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
191
  version: 2.5.0
172
192
  required_rubygems_version: !ruby/object:Gem::Requirement
173
193
  requirements:
174
- - - ">"
194
+ - - ">="
175
195
  - !ruby/object:Gem::Version
176
- version: 1.3.1
196
+ version: '0'
177
197
  requirements: []
178
198
  rubygems_version: 3.2.3
179
199
  signing_key: