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.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -7
  3. data/CHANGELOG.md +65 -6
  4. data/activeadmin.gemspec +1 -1
  5. data/config/locales/en.yml +1 -0
  6. data/config/locales/eo.yml +144 -0
  7. data/config/locales/it.yml +8 -3
  8. data/config/locales/ja.yml +5 -0
  9. data/config/locales/pt-BR.yml +8 -0
  10. data/config/locales/sv-SE.yml +2 -0
  11. data/config/locales/vi.yml +47 -6
  12. data/docs/2-resource-customization.md +1 -1
  13. data/features/index/filters.feature +11 -0
  14. data/features/new_page.feature +29 -0
  15. data/features/step_definitions/filter_steps.rb +9 -10
  16. data/gemfiles/rails_50.gemfile +1 -1
  17. data/gemfiles/rails_51.gemfile +1 -1
  18. data/lib/active_admin/csv_builder.rb +6 -4
  19. data/lib/active_admin/filters/active_filter.rb +46 -10
  20. data/lib/active_admin/form_builder.rb +1 -1
  21. data/lib/active_admin/inputs/filters/select_input.rb +5 -1
  22. data/lib/active_admin/orm/active_record/comments/views/active_admin_comments.rb +8 -3
  23. data/lib/active_admin/resource_controller/data_access.rb +5 -1
  24. data/lib/active_admin/resource_dsl.rb +2 -0
  25. data/lib/active_admin/version.rb +1 -1
  26. data/lib/active_admin/view_helpers/display_helper.rb +2 -2
  27. data/lib/bug_report_templates/active_admin_master.rb +0 -1
  28. data/lib/generators/active_admin/install/install_generator.rb +1 -1
  29. data/lib/generators/active_admin/install/templates/{admin_user.rb.erb → admin_users.rb.erb} +0 -0
  30. data/lib/generators/active_admin/resource/resource_generator.rb +1 -1
  31. data/spec/support/rails_template.rb +3 -0
  32. data/spec/support/rails_template_with_data.rb +5 -5
  33. data/spec/support/templates/post_decorator.rb +14 -0
  34. data/spec/unit/action_builder_spec.rb +33 -0
  35. data/spec/unit/csv_builder_spec.rb +9 -0
  36. data/spec/unit/filters/active_filter_spec.rb +54 -0
  37. data/spec/unit/filters/filter_form_builder_spec.rb +22 -0
  38. data/spec/unit/pretty_format_spec.rb +9 -1
  39. data/spec/unit/resource_controller/data_access_spec.rb +14 -10
  40. data/spec/unit/view_helpers/breadcrumbs_spec.rb +1 -0
  41. data/spec/unit/view_helpers/display_helper_spec.rb +11 -2
  42. 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/post.rb` file like so:
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:
@@ -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 "([^"]*)"( with value "([^"]*)")?$/) do |negative, key, compare_val, value|
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 compare_val
32
- expect(params[key]).to_not eq value if negative
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 "([^"]*)"( with label "([^"]*)")?$/ do |name, value, label_block, label|
42
- expect(page).to have_css "li.current_filter_#{name} span", text: label if label_block
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
 
@@ -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", branch: "rails-5", platforms: :jruby
8
+ gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter", platforms: :jruby
9
9
 
10
10
  gemspec path: "../"
@@ -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", branch: "rails-5", platforms: :jruby
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
- (1..paginated_collection.total_pages).each do |page|
57
- paginated_collection(page).each do |resource|
58
- resource = controller.send :apply_decorator, resource
59
- csv << CSV.generate_line(build_row(resource, columns, options), csv_options)
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.find(condition_values)
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 related_class
32
- "#{related_class.model_name.human} #{translated_predicate}".strip
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}".strip
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
- else
79
- assoc = condition_attribute.klass.reflect_on_all_associations.
80
- reject { |r| r.options[:polymorphic] }. #skip polymorphic
81
- detect { |r| r.foreign_key.to_s == name.to_s }
82
- assoc.class_name.constantize if assoc
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
@@ -100,7 +100,7 @@ module ActiveAdmin
100
100
  has_many_form.input builder_options[:sortable], as: :hidden
101
101
 
102
102
  contents << template.content_tag(:li, class: 'handle') do
103
- "MOVE"
103
+ I18n.t('active_admin.move')
104
104
  end
105
105
  end
106
106
 
@@ -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 '_id' if reflection
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? ? @comments.each(&method(:build_comment)) : build_empty_message
28
- div page_entries_info(@comments).html_safe, class: 'pagination_information'
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 method_for_build, *resource_params
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
 
@@ -1,3 +1,3 @@
1
1
  module ActiveAdmin
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -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 resource, display_name_method_for(resource) unless resource.nil?
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
@@ -22,7 +22,6 @@ gemfile(true) do
22
22
  gem 'sqlite3', platform: :mri
23
23
  gem 'activerecord-jdbcsqlite3-adapter',
24
24
  git: 'https://github.com/jruby/activerecord-jdbc-adapter',
25
- branch: 'rails-5',
26
25
  platform: :jruby
27
26
  end
28
27
 
@@ -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 'admin_user.rb.erb', "app/admin/#{name.underscore}.rb"
24
+ template 'admin_users.rb.erb', "app/admin/#{name.underscore.pluralize}.rb"
25
25
  end
26
26
  end
27
27
 
@@ -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/admin_user.rb', <<-RUBY, after: "ActiveAdmin.register AdminUser do\n"
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/category.rb', <<-RUBY, after: "ActiveAdmin.register Category do\n"
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/user.rb', <<-RUBY, after: "ActiveAdmin.register User do\n"
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/post.rb', <<-'RUBY', after: "ActiveAdmin.register Post do\n"
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/tag.rb', <<-RUBY, after: "ActiveAdmin.register Tag do\n"
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