introspective_admin 0.1.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.travis.yml +5 -14
- data/CHANGELOG.md +37 -28
- data/Gemfile +15 -6
- data/Gemfile.lock +347 -264
- data/README.md +34 -8
- data/introspective_admin.gemspec +34 -44
- data/lib/introspective_admin/base.rb +200 -200
- data/lib/introspective_admin/version.rb +3 -3
- data/spec/admin/company_admin_spec.rb +72 -72
- data/spec/admin/job_admin_spec.rb +61 -61
- data/spec/admin/location_admin_spec.rb +66 -66
- data/spec/admin/location_beacon_admin_spec.rb +73 -73
- data/spec/admin/project__admin_spec.rb +71 -71
- data/spec/admin/user_admin_spec.rb +64 -64
- data/spec/dummy/Gemfile +15 -0
- data/spec/dummy/README.rdoc +28 -28
- data/spec/dummy/Rakefile +6 -6
- data/spec/dummy/app/admin/admin_users.rb +28 -0
- data/spec/dummy/app/admin/company_admin.rb +4 -4
- data/spec/dummy/app/admin/dashboard.rb +32 -0
- data/spec/dummy/app/admin/job_admin.rb +4 -4
- data/spec/dummy/app/admin/location_admin.rb +4 -4
- data/spec/dummy/app/admin/location_beacon_admin.rb +6 -6
- data/spec/dummy/app/admin/project_admin.rb +6 -6
- data/spec/dummy/app/admin/role_admin.rb +5 -5
- data/spec/dummy/app/admin/user_admin.rb +13 -13
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/javascripts/active_admin.js +1 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -13
- data/spec/dummy/app/assets/stylesheets/active_admin.scss +17 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -15
- data/spec/dummy/app/controllers/application_controller.rb +8 -8
- data/spec/dummy/app/helpers/application_helper.rb +3 -3
- data/spec/dummy/app/models/abstract_adapter.rb +18 -12
- data/spec/dummy/app/models/admin_user.rb +10 -6
- data/spec/dummy/app/models/company.rb +12 -12
- data/spec/dummy/app/models/job.rb +10 -10
- data/spec/dummy/app/models/locatable.rb +6 -6
- data/spec/dummy/app/models/location.rb +26 -26
- data/spec/dummy/app/models/location_beacon.rb +19 -19
- data/spec/dummy/app/models/location_gps.rb +11 -11
- data/spec/dummy/app/models/project.rb +20 -20
- data/spec/dummy/app/models/project_job.rb +7 -7
- data/spec/dummy/app/models/role.rb +25 -25
- data/spec/dummy/app/models/team.rb +9 -9
- data/spec/dummy/app/models/team_user.rb +13 -13
- data/spec/dummy/app/models/user.rb +68 -68
- data/spec/dummy/app/models/user_location.rb +28 -28
- data/spec/dummy/app/models/user_project_job.rb +16 -16
- data/spec/dummy/app/views/layouts/application.html.erb +13 -13
- data/spec/dummy/bin/bundle +3 -3
- data/spec/dummy/bin/rails +4 -4
- data/spec/dummy/bin/rake +4 -4
- data/spec/dummy/bin/setup +29 -29
- data/spec/dummy/config/application.rb +34 -34
- data/spec/dummy/config/boot.rb +5 -5
- data/spec/dummy/config/database.yml +22 -22
- data/spec/dummy/config/environment.rb +11 -11
- data/spec/dummy/config/environments/development.rb +45 -45
- data/spec/dummy/config/environments/production.rb +82 -82
- data/spec/dummy/config/environments/test.rb +50 -50
- data/spec/dummy/config/initializers/active_admin.rb +7 -7
- data/spec/dummy/config/initializers/assets.rb +13 -13
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -3
- data/spec/dummy/config/initializers/devise.rb +263 -263
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -4
- data/spec/dummy/config/initializers/inflections.rb +16 -16
- data/spec/dummy/config/initializers/mime_types.rb +4 -4
- data/spec/dummy/config/initializers/session_store.rb +3 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
- data/spec/dummy/config/initializers/zeitwerk.rb +8 -0
- data/spec/dummy/config/locales/devise.en.yml +60 -60
- data/spec/dummy/config/locales/en.yml +23 -23
- data/spec/dummy/config/routes.rb +10 -9
- data/spec/dummy/config/secrets.yml +20 -20
- data/spec/dummy/config.ru +4 -4
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/development.sqlite3-shm +0 -0
- data/spec/dummy/db/development.sqlite3-wal +0 -0
- data/spec/dummy/db/migrate/20141002205024_devise_create_users.rb +42 -42
- data/spec/dummy/db/migrate/20141002211055_devise_create_admin_users.rb +48 -48
- data/spec/dummy/db/migrate/20141002211057_create_active_admin_comments.rb +19 -19
- data/spec/dummy/db/migrate/20141002220722_add_lockable_to_users.rb +8 -8
- data/spec/dummy/db/migrate/20150406213646_create_companies.rb +11 -11
- data/spec/dummy/db/migrate/20150414213154_add_user_authentication_token.rb +11 -11
- data/spec/dummy/db/migrate/20150415222005_create_roles.rb +12 -12
- data/spec/dummy/db/migrate/20150505181635_create_chats.rb +9 -9
- data/spec/dummy/db/migrate/20150505181636_create_chat_users.rb +11 -11
- data/spec/dummy/db/migrate/20150505181640_create_chat_messages.rb +11 -11
- data/spec/dummy/db/migrate/20150507191529_create_chat_message_users.rb +11 -11
- data/spec/dummy/db/migrate/20150601200526_create_locations.rb +13 -13
- data/spec/dummy/db/migrate/20150601200533_create_locatables.rb +10 -10
- data/spec/dummy/db/migrate/20150601212924_create_location_beacons.rb +16 -16
- data/spec/dummy/db/migrate/20150601213542_create_location_gps.rb +12 -12
- data/spec/dummy/db/migrate/20150609201823_create_user_locations.rb +14 -14
- data/spec/dummy/db/migrate/20150617232519_create_projects.rb +10 -10
- data/spec/dummy/db/migrate/20150617232521_create_jobs.rb +9 -9
- data/spec/dummy/db/migrate/20150617232522_create_project_jobs.rb +11 -11
- data/spec/dummy/db/migrate/20150623170133_create_user_project_jobs.rb +12 -12
- data/spec/dummy/db/migrate/20150701234929_create_teams.rb +11 -11
- data/spec/dummy/db/migrate/20150701234930_create_team_users.rb +11 -11
- data/spec/dummy/db/migrate/20150727214950_add_confirmable_to_devise.rb +11 -11
- data/spec/dummy/db/migrate/20150820190524_add_user_names.rb +6 -6
- data/spec/dummy/db/migrate/20150909225019_add_password_to_project.rb +5 -5
- data/spec/dummy/db/migrate/20220806003731_add_devise_to_admin_users.rb +51 -0
- data/spec/dummy/db/schema.rb +264 -264
- data/spec/dummy/introspective_admin.gemspec +34 -0
- data/spec/dummy/public/404.html +67 -67
- data/spec/dummy/public/422.html +67 -67
- data/spec/dummy/public/500.html +66 -66
- data/spec/rails_helper.rb +27 -27
- metadata +25 -116
- 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.
|
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
|
-
|
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/introspective_admin.gemspec
CHANGED
@@ -1,44 +1,34 @@
|
|
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 = '>=
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
+
$:.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 = '>= 2.7.0'
|
22
|
+
|
23
|
+
s.add_dependency 'rails'
|
24
|
+
s.add_dependency 'activeadmin'
|
25
|
+
s.add_dependency 'sass-rails'
|
26
|
+
s.add_dependency 'sass'
|
27
|
+
|
28
|
+
if RUBY_PLATFORM == 'java'
|
29
|
+
s.add_development_dependency "activerecord-jdbcsqlite3-adapter"
|
30
|
+
else
|
31
|
+
s.add_development_dependency "sqlite3"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -1,200 +1,200 @@
|
|
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.
|
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 { ' '.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
|
+
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_all_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 { ' '.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,3 +1,3 @@
|
|
1
|
-
module IntrospectiveAdmin
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
1
|
+
module IntrospectiveAdmin
|
2
|
+
VERSION = "0.9.0"
|
3
|
+
end
|