activeadmin 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeadmin might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +5 -7
- data/CHANGELOG.md +65 -6
- data/activeadmin.gemspec +1 -1
- data/config/locales/en.yml +1 -0
- data/config/locales/eo.yml +144 -0
- data/config/locales/it.yml +8 -3
- data/config/locales/ja.yml +5 -0
- data/config/locales/pt-BR.yml +8 -0
- data/config/locales/sv-SE.yml +2 -0
- data/config/locales/vi.yml +47 -6
- data/docs/2-resource-customization.md +1 -1
- data/features/index/filters.feature +11 -0
- data/features/new_page.feature +29 -0
- data/features/step_definitions/filter_steps.rb +9 -10
- data/gemfiles/rails_50.gemfile +1 -1
- data/gemfiles/rails_51.gemfile +1 -1
- data/lib/active_admin/csv_builder.rb +6 -4
- data/lib/active_admin/filters/active_filter.rb +46 -10
- data/lib/active_admin/form_builder.rb +1 -1
- data/lib/active_admin/inputs/filters/select_input.rb +5 -1
- data/lib/active_admin/orm/active_record/comments/views/active_admin_comments.rb +8 -3
- data/lib/active_admin/resource_controller/data_access.rb +5 -1
- data/lib/active_admin/resource_dsl.rb +2 -0
- data/lib/active_admin/version.rb +1 -1
- data/lib/active_admin/view_helpers/display_helper.rb +2 -2
- data/lib/bug_report_templates/active_admin_master.rb +0 -1
- data/lib/generators/active_admin/install/install_generator.rb +1 -1
- data/lib/generators/active_admin/install/templates/{admin_user.rb.erb → admin_users.rb.erb} +0 -0
- data/lib/generators/active_admin/resource/resource_generator.rb +1 -1
- data/spec/support/rails_template.rb +3 -0
- data/spec/support/rails_template_with_data.rb +5 -5
- data/spec/support/templates/post_decorator.rb +14 -0
- data/spec/unit/action_builder_spec.rb +33 -0
- data/spec/unit/csv_builder_spec.rb +9 -0
- data/spec/unit/filters/active_filter_spec.rb +54 -0
- data/spec/unit/filters/filter_form_builder_spec.rb +22 -0
- data/spec/unit/pretty_format_spec.rb +9 -1
- data/spec/unit/resource_controller/data_access_spec.rb +14 -10
- data/spec/unit/view_helpers/breadcrumbs_spec.rb +1 -0
- data/spec/unit/view_helpers/display_helper_spec.rb +11 -2
- metadata +7 -6
@@ -10,7 +10,7 @@ resource you must first create a Rails model for it.
|
|
10
10
|
## Create a Resource
|
11
11
|
|
12
12
|
The basic command for creating a resource is `rails g active_admin:resource Post`.
|
13
|
-
The generator will produce an empty `app/admin/
|
13
|
+
The generator will produce an empty `app/admin/posts.rb` file like so:
|
14
14
|
|
15
15
|
```ruby
|
16
16
|
ActiveAdmin.register Post do
|
@@ -23,6 +23,17 @@ Feature: Index Filtering
|
|
23
23
|
And I should see "Hello World 2" within ".index_table"
|
24
24
|
And I should see current filter "title_contains" equal to "Hello World 2" with label "Title contains"
|
25
25
|
|
26
|
+
Scenario: No XSS in Resources Filters
|
27
|
+
Given an index configuration of:
|
28
|
+
"""
|
29
|
+
ActiveAdmin.register Post do
|
30
|
+
filter :title
|
31
|
+
end
|
32
|
+
"""
|
33
|
+
When I fill in "Title" with "<script>alert('hax')</script>"
|
34
|
+
And I press "Filter"
|
35
|
+
Then I should see current filter "title_contains" equal to "alert('hax')" with label "Title contains"
|
36
|
+
|
26
37
|
Scenario: Filtering posts with no results
|
27
38
|
Given 3 posts exist
|
28
39
|
And an index configuration of:
|
data/features/new_page.feature
CHANGED
@@ -56,6 +56,35 @@ Feature: New Page
|
|
56
56
|
And I should see the attribute "Title" with "Hello World"
|
57
57
|
And I should see the attribute "Body" with "This is the body"
|
58
58
|
|
59
|
+
Scenario: Generating a custom form decorated with virtual attributes
|
60
|
+
Given a configuration of:
|
61
|
+
"""
|
62
|
+
ActiveAdmin.register Post do
|
63
|
+
decorate_with PostDecorator
|
64
|
+
permit_params :custom_category_id, :author_id, :virtual_title, :body, :published_date, :starred
|
65
|
+
|
66
|
+
form decorate: true do |f|
|
67
|
+
f.inputs "Your Post" do
|
68
|
+
f.input :virtual_title
|
69
|
+
f.input :body
|
70
|
+
end
|
71
|
+
f.inputs "Publishing" do
|
72
|
+
f.input :published_date
|
73
|
+
end
|
74
|
+
f.actions
|
75
|
+
end
|
76
|
+
end
|
77
|
+
"""
|
78
|
+
Given I follow "New Post"
|
79
|
+
Then I should see a fieldset titled "Your Post"
|
80
|
+
And I should see a fieldset titled "Publishing"
|
81
|
+
When I fill in "Virtual title" with "Hello World"
|
82
|
+
And I fill in "Body" with "This is the body"
|
83
|
+
And I press "Create Post"
|
84
|
+
Then I should see "Post was successfully created."
|
85
|
+
And I should see the attribute "Title" with "Hello World"
|
86
|
+
And I should see the attribute "Body" with "This is the body"
|
87
|
+
|
59
88
|
Scenario: Generating a form from a partial
|
60
89
|
Given "app/views/admin/posts/_form.html.erb" contains:
|
61
90
|
"""
|
@@ -22,24 +22,23 @@ Given(/^I add parameter "([^"]*)" with value "([^"]*)" to the URL$/) do |key, va
|
|
22
22
|
visit url + separator + key.to_s + '=' + value.to_s
|
23
23
|
end
|
24
24
|
|
25
|
-
Then(/^I should( not)? have parameter "([^"]*)"
|
25
|
+
Then(/^I should( not)? have parameter "([^"]*)" with value "([^"]*)"$/) do |negative, key, value|
|
26
26
|
query = URI(page.current_url).query
|
27
27
|
if query.nil?
|
28
28
|
expect(negative).to eq true
|
29
29
|
else
|
30
30
|
params = Rack::Utils.parse_query query
|
31
|
-
if
|
32
|
-
|
33
|
-
expect(params[key]).to eq value unless negative
|
34
|
-
else
|
35
|
-
expect(params[key]).to eq nil if negative
|
36
|
-
expect(params[key]).to be_present unless negative
|
37
|
-
end
|
31
|
+
expect(params[key]).to_not eq value if negative
|
32
|
+
expect(params[key]).to eq value unless negative
|
38
33
|
end
|
39
34
|
end
|
40
35
|
|
41
|
-
Then /^I should see current filter "([^"]*)" equal to "([^"]*)"
|
42
|
-
expect(page).to have_css "li.current_filter_#{name} span", text: label
|
36
|
+
Then /^I should see current filter "([^"]*)" equal to "([^"]*)" with label "([^"]*)"$/ do |name, value, label|
|
37
|
+
expect(page).to have_css "li.current_filter_#{name} span", text: label
|
38
|
+
expect(page).to have_css "li.current_filter_#{name} b", text: value
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /^I should see current filter "([^"]*)" equal to "([^"]*)"$/ do |name, value|
|
43
42
|
expect(page).to have_css "li.current_filter_#{name} b", text: value
|
44
43
|
end
|
45
44
|
|
data/gemfiles/rails_50.gemfile
CHANGED
@@ -5,6 +5,6 @@ eval_gemfile(File.expand_path(File.join("..", "Gemfile"), __dir__))
|
|
5
5
|
gem "rails", "5.0.3"
|
6
6
|
gem "devise", "~> 4.0"
|
7
7
|
gem "draper", "~> 3.0"
|
8
|
-
gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter",
|
8
|
+
gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter", platforms: :jruby
|
9
9
|
|
10
10
|
gemspec path: "../"
|
data/gemfiles/rails_51.gemfile
CHANGED
@@ -5,6 +5,6 @@ eval_gemfile(File.expand_path(File.join("..", "Gemfile"), __dir__))
|
|
5
5
|
gem "rails", "5.1.1"
|
6
6
|
gem "devise", "~> 4.3"
|
7
7
|
gem "draper", "~> 3.0"
|
8
|
-
gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter",
|
8
|
+
gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter", platforms: :jruby
|
9
9
|
|
10
10
|
gemspec path: "../"
|
@@ -53,10 +53,12 @@ module ActiveAdmin
|
|
53
53
|
csv << CSV.generate_line(columns.map{ |c| encode c.name, options }, csv_options)
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
|
-
paginated_collection
|
58
|
-
|
59
|
-
|
56
|
+
ActiveRecord::Base.uncached do
|
57
|
+
(1..paginated_collection.total_pages).each do |page|
|
58
|
+
paginated_collection(page).each do |resource|
|
59
|
+
resource = controller.send :apply_decorator, resource
|
60
|
+
csv << CSV.generate_line(build_row(resource, columns, options), csv_options)
|
61
|
+
end
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
@@ -19,7 +19,7 @@ module ActiveAdmin
|
|
19
19
|
def values
|
20
20
|
condition_values = condition.values.map(&:value)
|
21
21
|
if related_class
|
22
|
-
related_class.
|
22
|
+
related_class.where(related_primary_key => condition_values)
|
23
23
|
else
|
24
24
|
condition_values
|
25
25
|
end
|
@@ -28,11 +28,13 @@ module ActiveAdmin
|
|
28
28
|
def label
|
29
29
|
# TODO: to remind us to go back to the simpler str.downcase once we support ruby >= 2.4 only.
|
30
30
|
translated_predicate = predicate_name.mb_chars.downcase.to_s
|
31
|
-
if
|
32
|
-
"#{
|
31
|
+
if filter_label
|
32
|
+
"#{filter_label} #{translated_predicate}"
|
33
|
+
elsif related_class
|
34
|
+
"#{related_class_name} #{translated_predicate}"
|
33
35
|
else
|
34
|
-
"#{attribute_name} #{translated_predicate}"
|
35
|
-
end
|
36
|
+
"#{attribute_name} #{translated_predicate}"
|
37
|
+
end.strip
|
36
38
|
end
|
37
39
|
|
38
40
|
def predicate_name
|
@@ -54,6 +56,18 @@ module ActiveAdmin
|
|
54
56
|
resource_class.human_attribute_name(name)
|
55
57
|
end
|
56
58
|
|
59
|
+
def related_class_name
|
60
|
+
return unless related_class
|
61
|
+
|
62
|
+
related_class.model_name.human
|
63
|
+
end
|
64
|
+
|
65
|
+
def filter_label
|
66
|
+
return unless filter
|
67
|
+
|
68
|
+
filter[:label]
|
69
|
+
end
|
70
|
+
|
57
71
|
#@return Ransack::Nodes::Attribute
|
58
72
|
def condition_attribute
|
59
73
|
condition.attributes[0]
|
@@ -75,13 +89,35 @@ module ActiveAdmin
|
|
75
89
|
def find_class
|
76
90
|
if condition_attribute.klass != resource_class && condition_attribute.klass.primary_key == name.to_s
|
77
91
|
condition_attribute.klass
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
92
|
+
elsif predicate_association
|
93
|
+
predicate_association.klass
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def filter
|
98
|
+
resource.filters[name.to_sym]
|
99
|
+
end
|
100
|
+
|
101
|
+
def related_primary_key
|
102
|
+
if predicate_association
|
103
|
+
predicate_association.active_record_primary_key
|
104
|
+
elsif related_class
|
105
|
+
related_class.primary_key
|
83
106
|
end
|
84
107
|
end
|
108
|
+
|
109
|
+
def predicate_association
|
110
|
+
@predicate_association = find_predicate_association unless defined?(@predicate_association)
|
111
|
+
@predicate_association
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def find_predicate_association
|
117
|
+
condition_attribute.klass.reflect_on_all_associations.
|
118
|
+
reject { |r| r.options[:polymorphic] }. #skip polymorphic
|
119
|
+
detect { |r| r.foreign_key.to_s == name.to_s }
|
120
|
+
end
|
85
121
|
end
|
86
122
|
end
|
87
123
|
end
|
@@ -15,7 +15,7 @@ module ActiveAdmin
|
|
15
15
|
"#{reflection.through_reflection.name}_#{reflection.foreign_key}"
|
16
16
|
else
|
17
17
|
name = method.to_s
|
18
|
-
name.concat
|
18
|
+
name.concat "_#{reflection.association_primary_key}" if reflection_searchable?
|
19
19
|
name
|
20
20
|
end
|
21
21
|
end
|
@@ -48,6 +48,10 @@ module ActiveAdmin
|
|
48
48
|
klass.reorder("#{method} asc").distinct.pluck method
|
49
49
|
end
|
50
50
|
|
51
|
+
def reflection_searchable?
|
52
|
+
reflection && !reflection.polymorphic?
|
53
|
+
end
|
54
|
+
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
@@ -12,7 +12,7 @@ module ActiveAdmin
|
|
12
12
|
|
13
13
|
def build(resource)
|
14
14
|
@resource = resource
|
15
|
-
@comments = ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name).page(params[:page])
|
15
|
+
@comments = ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name).includes(:author).page(params[:page])
|
16
16
|
super(title, for: resource)
|
17
17
|
build_comments
|
18
18
|
end
|
@@ -24,8 +24,13 @@ module ActiveAdmin
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def build_comments
|
27
|
-
@comments.any?
|
28
|
-
|
27
|
+
if @comments.any?
|
28
|
+
@comments.each(&method(:build_comment))
|
29
|
+
div page_entries_info(@comments).html_safe, class: 'pagination_information'
|
30
|
+
else
|
31
|
+
build_empty_message
|
32
|
+
end
|
33
|
+
|
29
34
|
text_node paginate @comments
|
30
35
|
build_comment_form
|
31
36
|
end
|
@@ -115,6 +115,7 @@ module ActiveAdmin
|
|
115
115
|
get_resource_ivar || begin
|
116
116
|
resource = build_new_resource
|
117
117
|
resource = apply_decorations(resource)
|
118
|
+
resource = assign_attributes(resource, resource_params)
|
118
119
|
run_build_callbacks resource
|
119
120
|
authorize_resource! resource
|
120
121
|
|
@@ -127,7 +128,10 @@ module ActiveAdmin
|
|
127
128
|
#
|
128
129
|
# @return [ActiveRecord::Base] An un-saved active record base object
|
129
130
|
def build_new_resource
|
130
|
-
scoped_collection.send
|
131
|
+
scoped_collection.send(
|
132
|
+
method_for_build,
|
133
|
+
*resource_params.map { |params| params.slice(active_admin_config.resource_class.inheritance_column) }
|
134
|
+
)
|
131
135
|
end
|
132
136
|
|
133
137
|
# Calls all the appropriate callbacks and then creates the new resource.
|
@@ -131,6 +131,8 @@ module ActiveAdmin
|
|
131
131
|
# action.
|
132
132
|
#
|
133
133
|
def action(set, name, options = {}, &block)
|
134
|
+
warn "Warning: method `#{name}` already defined" if controller.method_defined?(name)
|
135
|
+
|
134
136
|
set << ControllerAction.new(name, options)
|
135
137
|
title = options.delete(:title)
|
136
138
|
|
data/lib/active_admin/version.rb
CHANGED
@@ -15,7 +15,7 @@ module ActiveAdmin
|
|
15
15
|
# Attempts to call any known display name methods on the resource.
|
16
16
|
# See the setting in `application.rb` for the list of methods and their priority.
|
17
17
|
def display_name(resource)
|
18
|
-
render_in_context
|
18
|
+
sanitize(render_in_context(resource, display_name_method_for(resource)).to_s) unless resource.nil?
|
19
19
|
end
|
20
20
|
|
21
21
|
# Looks up and caches the first available display name method.
|
@@ -66,7 +66,7 @@ module ActiveAdmin
|
|
66
66
|
def pretty_format(object)
|
67
67
|
case object
|
68
68
|
when String, Numeric, Symbol, Arbre::Element
|
69
|
-
object.to_s
|
69
|
+
sanitize(object.to_s)
|
70
70
|
when Date, Time
|
71
71
|
I18n.localize object, format: active_admin_application.localize_format
|
72
72
|
else
|
@@ -21,7 +21,7 @@ module ActiveAdmin
|
|
21
21
|
template 'dashboard.rb', 'app/admin/dashboard.rb'
|
22
22
|
if options[:users].present?
|
23
23
|
@user_class = name
|
24
|
-
template '
|
24
|
+
template 'admin_users.rb.erb', "app/admin/#{name.underscore.pluralize}.rb"
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
File without changes
|
@@ -12,7 +12,7 @@ module ActiveAdmin
|
|
12
12
|
|
13
13
|
def generate_config_file
|
14
14
|
@boilerplate = ActiveAdmin::Generators::Boilerplate.new(class_name)
|
15
|
-
template "admin.rb.erb", "app/admin/#{file_path.tr('/', '_')}.rb"
|
15
|
+
template "admin.rb.erb", "app/admin/#{file_path.tr('/', '_').pluralize}.rb"
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
@@ -50,9 +50,12 @@ generate :model, 'profile user_id:integer bio:text'
|
|
50
50
|
generate :model, 'user type:string first_name:string last_name:string username:string age:integer'
|
51
51
|
create_file 'app/models/user.rb', <<-RUBY.strip_heredoc, force: true
|
52
52
|
class User < ActiveRecord::Base
|
53
|
+
class VIP < self
|
54
|
+
end
|
53
55
|
has_many :posts, foreign_key: 'author_id'
|
54
56
|
has_one :profile
|
55
57
|
accepts_nested_attributes_for :profile, allow_destroy: true
|
58
|
+
accepts_nested_attributes_for :posts, allow_destroy: true
|
56
59
|
|
57
60
|
ransacker :age_in_five_years, type: :numeric, formatter: proc { |v| v.to_i - 5 } do |parent|
|
58
61
|
parent.table[:age]
|
@@ -5,7 +5,7 @@ inject_into_file 'config/initializers/active_admin.rb', <<-RUBY, after: "ActiveA
|
|
5
5
|
config.comments_menu = { parent: 'Administrative' }
|
6
6
|
RUBY
|
7
7
|
|
8
|
-
inject_into_file 'app/admin/
|
8
|
+
inject_into_file 'app/admin/admin_users.rb', <<-RUBY, after: "ActiveAdmin.register AdminUser do\n"
|
9
9
|
|
10
10
|
menu parent: "Administrative", priority: 1
|
11
11
|
RUBY
|
@@ -80,14 +80,14 @@ RUBY
|
|
80
80
|
generate :'active_admin:resource', type
|
81
81
|
end
|
82
82
|
|
83
|
-
inject_into_file 'app/admin/
|
83
|
+
inject_into_file 'app/admin/categories.rb', <<-RUBY, after: "ActiveAdmin.register Category do\n"
|
84
84
|
|
85
85
|
config.create_another = true
|
86
86
|
|
87
87
|
permit_params [:name, :description]
|
88
88
|
RUBY
|
89
89
|
|
90
|
-
inject_into_file 'app/admin/
|
90
|
+
inject_into_file 'app/admin/users.rb', <<-RUBY, after: "ActiveAdmin.register User do\n"
|
91
91
|
|
92
92
|
config.create_another = true
|
93
93
|
|
@@ -135,7 +135,7 @@ inject_into_file 'app/admin/user.rb', <<-RUBY, after: "ActiveAdmin.register User
|
|
135
135
|
end
|
136
136
|
RUBY
|
137
137
|
|
138
|
-
inject_into_file 'app/admin/
|
138
|
+
inject_into_file 'app/admin/posts.rb', <<-'RUBY', after: "ActiveAdmin.register Post do\n"
|
139
139
|
|
140
140
|
permit_params :custom_category_id, :author_id, :title, :body, :published_date, :position, :starred, taggings_attributes: [ :id, :tag_id, :name, :position, :_destroy ]
|
141
141
|
|
@@ -269,7 +269,7 @@ inject_into_file 'app/admin/post.rb', <<-'RUBY', after: "ActiveAdmin.register Po
|
|
269
269
|
end
|
270
270
|
RUBY
|
271
271
|
|
272
|
-
inject_into_file 'app/admin/
|
272
|
+
inject_into_file 'app/admin/tags.rb', <<-RUBY, after: "ActiveAdmin.register Tag do\n"
|
273
273
|
|
274
274
|
config.create_another = true
|
275
275
|
|
@@ -4,6 +4,20 @@ class PostDecorator < Draper::Decorator
|
|
4
4
|
decorates :post
|
5
5
|
delegate_all
|
6
6
|
|
7
|
+
# @param attributes [Hash]
|
8
|
+
def assign_attributes(attributes)
|
9
|
+
object.assign_attributes attributes.except(:virtual_title)
|
10
|
+
self.virtual_title = attributes.fetch(:virtual_title) if attributes.key?(:virtual_title)
|
11
|
+
end
|
12
|
+
|
13
|
+
def virtual_title
|
14
|
+
object.title
|
15
|
+
end
|
16
|
+
|
17
|
+
def virtual_title=(virtual_title)
|
18
|
+
object.title = virtual_title
|
19
|
+
end
|
20
|
+
|
7
21
|
def decorator_method
|
8
22
|
'A method only available on the decorator'
|
9
23
|
end
|
@@ -122,4 +122,37 @@ RSpec.describe 'defining actions from registration blocks', type: :controller do
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
125
|
+
|
126
|
+
context 'when method with given name is already defined' do
|
127
|
+
around :each do |example|
|
128
|
+
original_stderr = $stderr
|
129
|
+
$stderr = StringIO.new
|
130
|
+
example.run
|
131
|
+
$stderr = original_stderr
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'defining member action' do
|
135
|
+
let :action! do
|
136
|
+
ActiveAdmin.register Post do
|
137
|
+
member_action :process
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'writes warning to $stderr' do
|
142
|
+
expect($stderr.string).to include('Warning: method `process` already defined')
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'defining collection action' do
|
147
|
+
let :action! do
|
148
|
+
ActiveAdmin.register Post do
|
149
|
+
collection_action :process
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'writes warning to $stderr' do
|
154
|
+
expect($stderr.string).to include('Warning: method `process` already defined')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
125
158
|
end
|