introspective_admin 0.1.0 → 1.0.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +75 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +5 -14
  7. data/CHANGELOG.md +37 -28
  8. data/Gemfile +24 -6
  9. data/Gemfile.lock +402 -264
  10. data/README.md +34 -8
  11. data/Rakefile +3 -5
  12. data/introspective_admin.gemspec +29 -44
  13. data/lib/introspective_admin/base.rb +217 -200
  14. data/lib/introspective_admin/version.rb +5 -3
  15. data/lib/introspective_admin.rb +2 -0
  16. data/lib/tasks/introspective_admin_tasks.rake +2 -0
  17. data/spec/admin/company_admin_spec.rb +73 -72
  18. data/spec/admin/job_admin_spec.rb +63 -61
  19. data/spec/admin/location_admin_spec.rb +70 -66
  20. data/spec/admin/location_beacon_admin_spec.rb +75 -73
  21. data/spec/admin/project__admin_spec.rb +73 -71
  22. data/spec/admin/user_admin_spec.rb +65 -64
  23. data/spec/dummy/Gemfile +17 -0
  24. data/spec/dummy/README.rdoc +28 -28
  25. data/spec/dummy/Rakefile +8 -6
  26. data/spec/dummy/app/admin/admin_users.rb +29 -0
  27. data/spec/dummy/app/admin/company_admin.rb +5 -4
  28. data/spec/dummy/app/admin/dashboard.rb +34 -0
  29. data/spec/dummy/app/admin/job_admin.rb +5 -4
  30. data/spec/dummy/app/admin/location_admin.rb +5 -4
  31. data/spec/dummy/app/admin/location_beacon_admin.rb +8 -6
  32. data/spec/dummy/app/admin/project_admin.rb +5 -6
  33. data/spec/dummy/app/admin/role_admin.rb +5 -5
  34. data/spec/dummy/app/admin/user_admin.rb +13 -13
  35. data/spec/dummy/app/assets/config/manifest.js +3 -0
  36. data/spec/dummy/app/assets/javascripts/active_admin.js +1 -0
  37. data/spec/dummy/app/assets/javascripts/application.js +13 -13
  38. data/spec/dummy/app/assets/stylesheets/active_admin.scss +17 -0
  39. data/spec/dummy/app/assets/stylesheets/application.css +15 -15
  40. data/spec/dummy/app/controllers/application_controller.rb +10 -8
  41. data/spec/dummy/app/helpers/application_helper.rb +4 -3
  42. data/spec/dummy/app/models/abstract_adapter.rb +20 -12
  43. data/spec/dummy/app/models/admin_user.rb +12 -6
  44. data/spec/dummy/app/models/company.rb +13 -12
  45. data/spec/dummy/app/models/job.rb +10 -10
  46. data/spec/dummy/app/models/locatable.rb +8 -6
  47. data/spec/dummy/app/models/location.rb +27 -26
  48. data/spec/dummy/app/models/location_beacon.rb +19 -19
  49. data/spec/dummy/app/models/location_gps.rb +11 -11
  50. data/spec/dummy/app/models/project.rb +20 -20
  51. data/spec/dummy/app/models/project_job.rb +8 -7
  52. data/spec/dummy/app/models/role.rb +26 -25
  53. data/spec/dummy/app/models/team.rb +10 -9
  54. data/spec/dummy/app/models/team_user.rb +14 -13
  55. data/spec/dummy/app/models/user.rb +72 -68
  56. data/spec/dummy/app/models/user_location.rb +28 -28
  57. data/spec/dummy/app/models/user_project_job.rb +17 -16
  58. data/spec/dummy/app/views/layouts/application.html.erb +13 -13
  59. data/spec/dummy/bin/bundle +5 -3
  60. data/spec/dummy/bin/rails +6 -4
  61. data/spec/dummy/bin/rake +6 -4
  62. data/spec/dummy/bin/setup +31 -29
  63. data/spec/dummy/config/application.rb +35 -34
  64. data/spec/dummy/config/boot.rb +7 -5
  65. data/spec/dummy/config/database.yml +22 -22
  66. data/spec/dummy/config/environment.rb +12 -11
  67. data/spec/dummy/config/environments/development.rb +47 -45
  68. data/spec/dummy/config/environments/production.rb +83 -82
  69. data/spec/dummy/config/environments/test.rb +50 -50
  70. data/spec/dummy/config/initializers/active_admin.rb +8 -7
  71. data/spec/dummy/config/initializers/assets.rb +15 -13
  72. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -7
  73. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -3
  74. data/spec/dummy/config/initializers/devise.rb +265 -263
  75. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -4
  76. data/spec/dummy/config/initializers/inflections.rb +18 -16
  77. data/spec/dummy/config/initializers/mime_types.rb +6 -4
  78. data/spec/dummy/config/initializers/session_store.rb +5 -3
  79. data/spec/dummy/config/initializers/wrap_parameters.rb +16 -14
  80. data/spec/dummy/config/initializers/zeitwerk.rb +10 -0
  81. data/spec/dummy/config/locales/devise.en.yml +60 -60
  82. data/spec/dummy/config/locales/en.yml +23 -23
  83. data/spec/dummy/config/routes.rb +12 -9
  84. data/spec/dummy/config/secrets.yml +20 -20
  85. data/spec/dummy/config.ru +6 -4
  86. data/spec/dummy/db/development.sqlite3 +0 -0
  87. data/spec/dummy/db/migrate/20141002205024_devise_create_users.rb +43 -42
  88. data/spec/dummy/db/migrate/20141002211055_devise_create_admin_users.rb +49 -48
  89. data/spec/dummy/db/migrate/20141002211057_create_active_admin_comments.rb +21 -19
  90. data/spec/dummy/db/migrate/20141002220722_add_lockable_to_users.rb +10 -8
  91. data/spec/dummy/db/migrate/20150406213646_create_companies.rb +13 -11
  92. data/spec/dummy/db/migrate/20150414213154_add_user_authentication_token.rb +13 -11
  93. data/spec/dummy/db/migrate/20150415222005_create_roles.rb +13 -12
  94. data/spec/dummy/db/migrate/20150505181635_create_chats.rb +11 -9
  95. data/spec/dummy/db/migrate/20150505181636_create_chat_users.rb +13 -11
  96. data/spec/dummy/db/migrate/20150505181640_create_chat_messages.rb +13 -11
  97. data/spec/dummy/db/migrate/20150507191529_create_chat_message_users.rb +13 -11
  98. data/spec/dummy/db/migrate/20150601200526_create_locations.rb +15 -13
  99. data/spec/dummy/db/migrate/20150601200533_create_locatables.rb +12 -10
  100. data/spec/dummy/db/migrate/20150601212924_create_location_beacons.rb +18 -16
  101. data/spec/dummy/db/migrate/20150601213542_create_location_gps.rb +14 -12
  102. data/spec/dummy/db/migrate/20150609201823_create_user_locations.rb +16 -14
  103. data/spec/dummy/db/migrate/20150617232519_create_projects.rb +12 -10
  104. data/spec/dummy/db/migrate/20150617232521_create_jobs.rb +11 -9
  105. data/spec/dummy/db/migrate/20150617232522_create_project_jobs.rb +13 -11
  106. data/spec/dummy/db/migrate/20150623170133_create_user_project_jobs.rb +14 -12
  107. data/spec/dummy/db/migrate/20150701234929_create_teams.rb +13 -11
  108. data/spec/dummy/db/migrate/20150701234930_create_team_users.rb +13 -11
  109. data/spec/dummy/db/migrate/20150727214950_add_confirmable_to_devise.rb +13 -11
  110. data/spec/dummy/db/migrate/20150820190524_add_user_names.rb +8 -6
  111. data/spec/dummy/db/migrate/20150909225019_add_password_to_project.rb +7 -5
  112. data/spec/dummy/db/migrate/20220806003731_add_devise_to_admin_users.rb +50 -0
  113. data/spec/dummy/db/schema.rb +263 -264
  114. data/spec/dummy/public/404.html +67 -67
  115. data/spec/dummy/public/422.html +67 -67
  116. data/spec/dummy/public/500.html +66 -66
  117. data/spec/rails_helper.rb +33 -27
  118. data/spec/support/blueprints.rb +50 -49
  119. data/spec/support/location_helper.rb +28 -29
  120. metadata +21 -252
  121. data/Gemfile.lock.rails4 +0 -256
data/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  IntrospectiveAdmin is a Rails Plugin for DRYing up ActiveAdmin configurations by
20
20
  laying out simple defaults and including nested relations according to the models'
21
- accepts_nested_attributes_for :relation declarations.
21
+ accepts_nested_attributes_for :relation declarations.
22
22
 
23
23
  ## Documentation
24
24
 
@@ -28,7 +28,9 @@ In your Gemfile:
28
28
  gem 'introspective_admin'
29
29
  ```
30
30
 
31
- And bundle install. In app/admin/my_admin.rb:
31
+ And bundle install.
32
+
33
+ In app/admin/my_admin.rb:
32
34
 
33
35
  ```
34
36
  class MyAdmin < IntrospectiveAdmin::Base
@@ -39,7 +41,7 @@ class MyAdmin < IntrospectiveAdmin::Base
39
41
  def self.exclude_params
40
42
  %w(fields to exclude from the admin screen)
41
43
  end
42
-
44
+
43
45
  register MyModel do
44
46
  # It yields the ActiveAdmin DSL context back, allowing further configuration to
45
47
  # be added here, just as you would normally, to the Admin::MyModelController
@@ -48,29 +50,32 @@ class MyAdmin < IntrospectiveAdmin::Base
48
50
  end
49
51
  ```
50
52
 
51
- Registering MyModel will set up the index, show, and form configurations for every attribute, virtual attribute listed in MyAdmin.include_virtual_attributes (e.g. a password field for a Devise model), and nested association on the model excluding those in MyAdmin.exclude_params. It will link to associated records (if they have ActiveAdmin screens), perform eager loading of nested associations, and permit parameters for every non-excluded attribute on the model.
53
+ Registering MyModel will set up the index, show, and form configurations for every attribute, virtual attribute listed in MyAdmin.include_virtual_attributes (e.g. a password field for a Devise model), and nested association on the model excluding those in MyAdmin.exclude_params. It will link to associated records (if they have ActiveAdmin screens), perform eager loading of nested associations, and permit parameters for every non-excluded attribute on the model.
52
54
 
53
- Customizing select box options for associations is done by adding an
55
+ Customizing select box options for associations is done by adding an
54
56
  "options_for_X" class method on the administrated model:
55
57
 
56
58
  ```
59
+ app/models/my_model.rb
57
60
  class MyModel < ActiveRecord::Base
58
61
  belongs_to :parent
59
62
  has_many :other_models
60
63
  accepts_nested_attributes_for :other_models, :allow_destroy => true
61
64
 
62
65
  def self.options_for_parent(instance_of_my_model)
63
- Parent.order(:appelation).map.{|p| ["#{p.appelation}", p.id] }
66
+ Parent.order(:appelation).map.{|p| ["#{p.appelation}", p.id] }
64
67
  end
65
68
  end
66
69
  ```
67
70
 
68
71
  IntrospectiveAdmin will detect nested polymorphic relations and attempt to handle
69
- them using virutal attributes that you must add to the model instance, plus a class
72
+ them using virutal attributes that you must add to the model instance, plus a class
70
73
  method for the select box options, using a shared delimiter string for the compound ID.
71
- E.g. here we use a hyphen:
74
+
75
+ E.g. here we use a hyphen:
72
76
 
73
77
  ```
78
+ app/models/my_model.rb
74
79
  class MyModel < ActiveRecord::Base
75
80
  belongs_to :poly_model, polymorphic: true
76
81
  accepts_nested_attributes_for :poly_model, :allow_destroy => true
@@ -90,6 +95,27 @@ class MyModel < ActiveRecord::Base
90
95
  end
91
96
  ```
92
97
 
98
+ ActiveAdmin relies on [Ransack](https://github.com/activerecord-hackery/ransack) to power its searches. You will need
99
+ to explicitly declare attributes and associations to be accessible to ActiveAdmin. You can defeat the purpose by declaring everything accessible via your models' abstract base class, but be sure to exclude sensitive data:
100
+
101
+ ```
102
+ app/models/application_record.rb
103
+ def ApplicationRecord < ActiveRecord::Base
104
+ self.abstract_class = true
105
+
106
+ class << self
107
+ def ransackable_attributes(auth_object = nil)
108
+ @ransackable_attributes ||= column_names + _ransackers.keys - %w(password)
109
+ end
110
+
111
+ def ransackable_associations(auth_object = nil)
112
+ @ransackable_associations ||= reflect_on_all_associations.map { |a| a.name.to_s } + _ransackers.keys
113
+ end
114
+ end
115
+ end
116
+ ```
117
+
118
+
93
119
  ## Dependencies
94
120
 
95
121
  Tool | Description
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -14,13 +16,9 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
16
  rdoc.rdoc_files.include('lib/**/*.rb')
15
17
  end
16
18
 
17
- APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
18
20
  load 'rails/tasks/engine.rake'
19
21
 
20
-
21
22
  load 'rails/tasks/statistics.rake'
22
23
 
23
-
24
-
25
24
  Bundler::GemHelper.install_tasks
26
-
@@ -1,44 +1,29 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
-
3
- # Maintain your gem's version:
4
- require "introspective_admin/version"
5
- require "introspective_admin/base"
6
-
7
- # Describe your gem and declare its dependencies:
8
- Gem::Specification.new do |s|
9
- s.name = "introspective_admin"
10
- s.version = IntrospectiveAdmin::VERSION
11
- s.authors = ["Josh Buermann"]
12
- s.email = ["buermann@gmail.com"]
13
- s.homepage = "https://github.com/buermann/introspective_admin"
14
- s.summary = "Set up basic ActiveAdmin screens for an ActiveRecord model."
15
- s.description = "Set up basic ActiveAdmin screens for an ActiveRecord model."
16
- s.license = "MIT"
17
-
18
- s.files = `git ls-files`.split("\n").sort
19
- s.test_files = `git ls-files -- spec/*`.split("\n")
20
-
21
- s.required_ruby_version = '>= 1.9.3'
22
-
23
- s.add_dependency 'rails'
24
- s.add_dependency 'activeadmin'
25
- s.add_dependency 'sass-rails'
26
- s.add_dependency 'sass'
27
-
28
-
29
- if RUBY_PLATFORM == 'java'
30
- s.add_development_dependency "activerecord-jdbcsqlite3-adapter"
31
- else
32
- s.add_development_dependency "sqlite3", '~> 1.3.6'
33
- end
34
- s.add_development_dependency "rspec-rails"
35
- s.add_development_dependency 'devise'
36
- s.add_development_dependency 'devise-async'
37
- s.add_development_dependency 'machinist_redux'
38
- s.add_development_dependency 'simplecov'
39
- s.add_development_dependency 'rufus-mnemo'
40
- s.add_development_dependency 'rails-controller-testing'
41
- s.add_development_dependency 'sprockets-rails'
42
-
43
- end
44
-
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ # Maintain your gem's version:
6
+ require 'introspective_admin/version'
7
+ require 'introspective_admin/base'
8
+
9
+ # Describe your gem and declare its dependencies:
10
+ Gem::Specification.new do |s|
11
+ s.name = 'introspective_admin'
12
+ s.version = IntrospectiveAdmin::VERSION
13
+ s.authors = ['Josh Buermann']
14
+ s.email = ['buermann@gmail.com']
15
+ s.homepage = 'https://github.com/buermann/introspective_admin'
16
+ s.summary = 'Set up basic ActiveAdmin screens for an ActiveRecord model.'
17
+ s.description = 'Set up basic ActiveAdmin screens for an ActiveRecord model.'
18
+ s.license = 'MIT'
19
+
20
+ s.files = `git ls-files`.split("\n").sort
21
+
22
+ s.required_ruby_version = '>= 3.0', '< 4'
23
+
24
+ s.add_dependency 'activeadmin'
25
+ s.add_dependency 'sass'
26
+ s.add_dependency 'sass-rails'
27
+
28
+ s.metadata['rubygems_mfa_required'] = 'true'
29
+ end
@@ -1,200 +1,217 @@
1
- module IntrospectiveAdmin
2
- class Base
3
- # Generate an active admin interface by introspecting on the models.
4
-
5
- # For polymorphic associations set up virtual 'assign' attributes on the model like so:
6
- #
7
- # def <polymorphism>_assign
8
- # "#{<polymorphism>_type}-#{<polymorphism>_id}"
9
- # end
10
- # def <polymorphism>_assign=(value)
11
- # self.<polymorphism>_type,self.<polymorphism>_id = value.split('-')
12
- # end
13
- #
14
- # And designate the selection options in a class method, you can pass the
15
- # target model to modify the options list accordingly
16
- #
17
- # def self.<polymorphism>_options(model=nil)
18
- # (Model.all + SecondModel.all).map { |i| [ i.name, "#{i.class}-#{i.id}"] }
19
- # end
20
-
21
- class << self
22
-
23
- def exclude_params
24
- [] # do not display the field in the index page and forms.
25
- end
26
-
27
- def include_virtual_attributes
28
- [] #
29
- end
30
-
31
- def polymorphic?(model,column)
32
- (model.reflections[column.sub(/_id$/,'')].try(:options)||{})[:polymorphic]
33
- end
34
-
35
- def column_list(model, extras=[])
36
- model.columns.map {|c|
37
- ref_name = c.name.sub(/(_type|_id)$/,'')
38
- model.reflections[ref_name] ? ref_name : c.name
39
- }.uniq-['created_at','updated_at']-exclude_params+extras
40
- end
41
-
42
- def params_list(model, extras=[])
43
- model.columns.map {|c|
44
- polymorphic?(model,c.name) ? c.name.sub(/_id$/,'')+"_assign" : c.name
45
- }+extras
46
- end
47
-
48
- def link_record(dsl, record)
49
- link_text = record.try(:name) || record.try(:title) || record.class.to_s
50
- link_href = begin dsl.send("admin_#{record.class.name.underscore}_path", record.id) rescue false end
51
- if link_href
52
- dsl.link_to link_text, link_href
53
- elsif link_text
54
- link_text
55
- else
56
- record
57
- end
58
- end
59
-
60
- def register(model, &block)
61
- # Defining activeadmin pages will break pending migrations:
62
- begin ActiveRecord::Migration.check_pending! rescue return end
63
-
64
- klass = self
65
- model_name = model.to_s.underscore
66
- nested_config = Hash[model.nested_attributes_options.reject {|name,o|
67
- klass.exclude_params.include?("#{name}_attributes")
68
- }.map {|assoc,options|
69
- reflection = model.reflections[assoc.to_s]
70
- reflection_class = reflection.class_name.constantize
71
- # merge the options of the nested attribute and relationship declarations
72
- options = options.merge(reflection_class.reflections[assoc.to_s].try(:options) || {})
73
- options[:class] = reflection_class
74
- options[:columns] = klass.column_list(reflection_class)
75
- options[:params] = klass.params_list(reflection_class)
76
- options[:reflection] = reflection
77
- options[:polymorphic_reference] = reflection.options[:as].to_s
78
- [assoc, options]
79
- }]
80
-
81
- ActiveAdmin.register model do
82
- instance_eval &block if block_given? # Evalutate the passed black for overrides to the defaults
83
-
84
- controller do
85
- def scoped_collection
86
- super.includes super.nested_attributes_options.keys
87
- end
88
- end
89
-
90
- index do
91
- selectable_column
92
- cols = model.columns.map(&:name)-klass.exclude_params
93
- cols.each_with_index do |c,i|
94
- reflection = model.reflections.detect {|k,v| v.foreign_key == c }
95
- if reflection
96
- column c do |o|
97
- klass.link_record(self,o.send(reflection.first))
98
- end
99
- else
100
- column c
101
- end
102
- end
103
- actions
104
- end
105
-
106
- show do
107
- instance = self.send(model_name)
108
-
109
- attributes_table do
110
- model.columns.each do |c|
111
- next if (c.name =~ /password/)
112
- reflection = model.reflections.detect {|k,v| v.foreign_key == c.name }
113
- if reflection
114
- row c.name do |o|
115
- klass.link_record(self,instance.send(reflection.first))
116
- end
117
- else
118
- row c.name
119
- end
120
-
121
- end
122
-
123
- nested_config.each do |assoc,options|
124
- panel assoc.capitalize do
125
- table_for instance.send(assoc) do
126
- options[:columns].each do |c|
127
- if options[:class].reflections[c]
128
- column( c ) do |r|
129
- klass.link_record(self,r.send(c)) if r
130
- end
131
- else
132
- column c
133
- end
134
- end
135
-
136
- admin_route = begin url_for(['admin', options[:class].name.underscore]) rescue false end
137
- if admin_route
138
- column 'actions' do |child|
139
- span link_to "View", url_for(['admin', child])
140
- span link_to "Edit", url_for(['edit','admin',child])
141
- span link_to "Delete", url_for(['admin',child]), method: :delete, confirm: "Are you sure you want to delete this?"
142
- end
143
- end
144
- end
145
- end
146
- end
147
- end
148
- end
149
-
150
- permit_params klass.params_list(model, klass.include_virtual_attributes) + [Hash[nested_config.map{|assoc,o|
151
- ["#{assoc}_attributes", o[:params]+[(o[:allow_destroy] ? :_destroy : '')] ]
152
- }]]
153
-
154
- form do |f|
155
- f.semantic_errors *f.object.errors.messages.keys
156
- f.actions
157
-
158
- klass.column_list(model, klass.include_virtual_attributes).each do |column|
159
- if column == model.primary_key
160
- elsif klass.polymorphic?(model,column)
161
- f.input column+"_assign", collection: model.send("#{column}_assign_options")
162
- elsif model.respond_to?("options_for_#{column}")
163
- f.input column, collection: model.send("options_for_#{column}", f.object)
164
- else
165
- f.input column
166
- end
167
- end
168
-
169
- div { '&nbsp'.html_safe }
170
-
171
- nested_config.each do |assoc,options|
172
- aclass = options[:class]
173
- columns = options[:columns]-[aclass.primary_key]
174
- f.inputs do
175
- f.has_many assoc, allow_destroy: options[:allow_destroy] do |r|
176
- columns.each do |c|
177
- if c == model_name || c == options[:polymorphic_reference]
178
- # the join to the parent is implicit
179
- elsif klass.polymorphic?(aclass,c)
180
- r.input "#{c}_assign", collection: aclass.send("#{c}_assign_options")
181
- elsif aclass.reflections[c] && aclass.respond_to?("options_for_#{c}")
182
- # If the class has an options_for_<column> method defined use that
183
- # rather than the default behavior, pass the instance for scoping,
184
- # e.g. UserProjectJob.options_for_job is scoped by the Project's
185
- # jobs:
186
- r.input c, collection: aclass.send("options_for_#{c}",f.object)
187
- else
188
- r.input c
189
- end
190
- end
191
- end
192
- end
193
- end
194
- end
195
-
196
- end
197
- end
198
- end
199
- end
200
- end
1
+ # frozen_string_literal: true
2
+
3
+ module IntrospectiveAdmin
4
+ class Base
5
+ # Generate an active admin interface by introspecting on the models.
6
+
7
+ # For polymorphic associations set up virtual 'assign' attributes on the model like so:
8
+ #
9
+ # def <polymorphism>_assign
10
+ # "#{<polymorphism>_type}-#{<polymorphism>_id}"
11
+ # end
12
+ # def <polymorphism>_assign=(value)
13
+ # self.<polymorphism>_type,self.<polymorphism>_id = value.split('-')
14
+ # end
15
+ #
16
+ # And designate the selection options in a class method, you can pass the
17
+ # target model to modify the options list accordingly
18
+ #
19
+ # def self.<polymorphism>_options(model=nil)
20
+ # (Model.all + SecondModel.all).map { |i| [ i.name, "#{i.class}-#{i.id}"] }
21
+ # end
22
+
23
+ class << self
24
+ def exclude_params
25
+ [] # do not display the field in the index page and forms.
26
+ end
27
+
28
+ def include_virtual_attributes
29
+ []
30
+ end
31
+
32
+ def polymorphic?(model, column)
33
+ (model.reflections[column.sub(/_id$/, '')].try(:options) || {})[:polymorphic]
34
+ end
35
+
36
+ def column_list(model, extras = [])
37
+ model.columns.map { |c|
38
+ ref_name = c.name.sub(/(_type|_id)$/, '')
39
+ model.reflections[ref_name] ? ref_name : c.name
40
+ }.uniq - %w[created_at updated_at] - exclude_params + extras
41
+ end
42
+
43
+ def params_list(model, extras = [])
44
+ model.columns.map { |c|
45
+ polymorphic?(model, c.name) ? "#{c.name.sub(/_id$/, '')}_assign" : c.name
46
+ } + extras
47
+ end
48
+
49
+ def link_record(dsl, record)
50
+ link_text = record.try(:name) || record.try(:title) || record.class.to_s
51
+ link_href = begin begin
52
+ dsl.send("admin_#{record.class.name.underscore}_path", record.id)
53
+ rescue StandardError
54
+ false
55
+ end
56
+ end
57
+ if link_href
58
+ dsl.link_to link_text, link_href
59
+ elsif link_text
60
+ link_text
61
+ else
62
+ record
63
+ end
64
+ end
65
+
66
+ def register(model, &block)
67
+ # Defining activeadmin pages will break pending migrations:
68
+ begin begin
69
+ ActiveRecord::Migration.check_all_pending!
70
+ rescue StandardError
71
+ return
72
+ end
73
+ end
74
+
75
+ klass = self
76
+ model_name = model.to_s.underscore
77
+ nested_config = model.nested_attributes_options.reject { |name, _o|
78
+ klass.exclude_params.include?("#{name}_attributes")
79
+ }.to_h do |assoc, options|
80
+ reflection = model.reflections[assoc.to_s]
81
+ reflection_class = reflection.class_name.constantize
82
+ # merge the options of the nested attribute and relationship declarations
83
+ options = options.merge(reflection_class.reflections[assoc.to_s].try(:options) || {})
84
+ options[:class] = reflection_class
85
+ options[:columns] = klass.column_list(reflection_class)
86
+ options[:params] = klass.params_list(reflection_class)
87
+ options[:reflection] = reflection
88
+ options[:polymorphic_reference] = reflection.options[:as].to_s
89
+ [assoc, options]
90
+ end
91
+
92
+ ActiveAdmin.register model do
93
+ instance_eval(&block) if block_given? # Evalutate the passed black for overrides to the defaults
94
+
95
+ controller do
96
+ def scoped_collection
97
+ super.includes super.nested_attributes_options.keys
98
+ end
99
+ end
100
+
101
+ index do
102
+ selectable_column
103
+ cols = model.columns.map(&:name) - klass.exclude_params
104
+ cols.each_with_index do |c, _i|
105
+ reflection = model.reflections.detect { |_k, v| v.foreign_key == c }
106
+ if reflection
107
+ column c do |o|
108
+ klass.link_record(self, o.send(reflection.first))
109
+ end
110
+ else
111
+ column c
112
+ end
113
+ end
114
+ actions
115
+ end
116
+
117
+ show do
118
+ instance = send(model_name)
119
+
120
+ attributes_table do
121
+ model.columns.each do |c|
122
+ next if c.name =~ /password/
123
+
124
+ reflection = model.reflections.detect { |_k, v| v.foreign_key == c.name }
125
+ if reflection
126
+ row c.name do |_o|
127
+ klass.link_record(self, instance.send(reflection.first))
128
+ end
129
+ else
130
+ row c.name
131
+ end
132
+ end
133
+
134
+ nested_config.each do |assoc, options|
135
+ panel assoc.capitalize do
136
+ table_for instance.send(assoc) do
137
+ options[:columns].each do |c|
138
+ if options[:class].reflections[c]
139
+ column(c) do |r|
140
+ klass.link_record(self, r.send(c)) if r
141
+ end
142
+ else
143
+ column c
144
+ end
145
+ end
146
+
147
+ admin_route = begin begin
148
+ url_for(['admin', options[:class].name.underscore])
149
+ rescue StandardError
150
+ false
151
+ end
152
+ end
153
+ if admin_route
154
+ column 'actions' do |child|
155
+ span link_to 'View', url_for(['admin', child])
156
+ span link_to 'Edit', url_for(['edit', 'admin', child])
157
+ span link_to 'Delete', url_for(['admin', child]), method: :delete, confirm: 'Are you sure you want to delete this?'
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ permitted_params = klass.params_list(model, klass.include_virtual_attributes) + [nested_config.to_h do |assoc, o|
167
+ ["#{assoc}_attributes", o[:params] + [(o[:allow_destroy] ? :_destroy : '')]]
168
+ end]
169
+ permit_params(*permitted_params)
170
+
171
+ form do |f|
172
+ f.semantic_errors(*f.object.errors.messages.keys)
173
+ f.actions
174
+
175
+ klass.column_list(model, klass.include_virtual_attributes).each do |column|
176
+ next if column == model.primary_key
177
+
178
+ if klass.polymorphic?(model, column)
179
+ f.input "#{column}_assign", collection: model.send("#{column}_assign_options")
180
+ elsif model.respond_to?("options_for_#{column}")
181
+ f.input column, collection: model.send("options_for_#{column}", f.object)
182
+ else
183
+ f.input column
184
+ end
185
+ end
186
+
187
+ div { '&nbsp'.html_safe }
188
+
189
+ nested_config.each do |assoc, options|
190
+ aclass = options[:class]
191
+ columns = options[:columns] - [aclass.primary_key]
192
+ f.inputs do
193
+ f.has_many assoc, allow_destroy: options[:allow_destroy] do |r|
194
+ columns.each do |c|
195
+ if c == model_name || c == options[:polymorphic_reference]
196
+ # the join to the parent is implicit
197
+ elsif klass.polymorphic?(aclass, c)
198
+ r.input "#{c}_assign", collection: aclass.send("#{c}_assign_options")
199
+ elsif aclass.reflections[c] && aclass.respond_to?("options_for_#{c}")
200
+ # If the class has an options_for_<column> method defined use that
201
+ # rather than the default behavior, pass the instance for scoping,
202
+ # e.g. UserProjectJob.options_for_job is scoped by the Project's
203
+ # jobs:
204
+ r.input c, collection: aclass.send("options_for_#{c}", f.object)
205
+ else
206
+ r.input c
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -1,3 +1,5 @@
1
- module IntrospectiveAdmin
2
- VERSION = "0.1.0"
3
- end
1
+ # frozen_string_literal: true
2
+
3
+ module IntrospectiveAdmin
4
+ VERSION = '1.0.0'
5
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'activeadmin'
2
4
  module IntrospectiveAdmin
3
5
  autoload :Base, 'introspective_admin/base'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # desc "Explaining what the task does"
2
4
  # task :introspective_admin do
3
5
  # # Task goes here