diff --git a/CHANGELOG.md b/CHANGELOG.md index 9741f15c033..404bf04a085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ #### Minor +* Do not auto link to inaccessible actions [#3686][] by [@pranas][] * Allow to enable comments on per-resource basis [#3695][] by [@pranas][] * Unify DSL for index `actions` and `actions dropdown: true` [#3463][] by [@timoschilling][] * Add DSL method `includes` for `ActiveRecord::Relation#includes` [#3464][] by [@timoschilling][] @@ -989,6 +990,7 @@ of the highlights. 250 commits. Enough said. [#3486]: https://github.com/activeadmin/activeadmin/issues/3486 [#3519]: https://github.com/activeadmin/activeadmin/issues/3519 [#3606]: https://github.com/activeadmin/activeadmin/issues/3606 +[#3686]: https://github.com/activeadmin/activeadmin/issues/3686 [#3695]: https://github.com/activeadmin/activeadmin/issues/3695 [#3731]: https://github.com/activeadmin/activeadmin/issues/3731 [@Bishop]: https://github.com/Bishop diff --git a/Gemfile b/Gemfile index c8ea4172ed0..ae7169ad666 100644 --- a/Gemfile +++ b/Gemfile @@ -7,8 +7,6 @@ require File.expand_path 'spec/support/detect_rails_version', File.dirname(__FIL rails_version = detect_rails_version gem 'rails', rails_version -gem 'inherited_resources', github: 'josevalim/inherited_resources', branch: 'rails-4-2' - # Optional dependencies gem 'cancan' gem 'devise' diff --git a/README.md b/README.md index 7a813c1cd88..bbf8b22968d 100644 --- a/README.md +++ b/README.md @@ -22,18 +22,6 @@ You can get Rails 4.x support by tracking master: gem 'activeadmin', github: 'activeadmin' ``` -#### Rails 4.2 - -To use ActiveAdmin with Rails 4.2, you need to change your Gemfile like this: - -```ruby -gem 'activeadmin', github: 'activeadmin' -gem 'inherited_resources', github: 'josevalim/inherited_resources', branch: 'rails-4-2' -``` - -*But keep in mind that `inherited_resources` still don't support Rails 4.2 officially.* -*Track [josevalim/inherited_resources#381](https://github.com/josevalim/inherited_resources/issues/381) for details.* - ### 0.6.x The plan is to follow [semantic versioning](http://semver.org/) as of 1.0.0. The 0.6.x line will diff --git a/activeadmin.gemspec b/activeadmin.gemspec index 22d98a0e834..f81f6ead74b 100644 --- a/activeadmin.gemspec +++ b/activeadmin.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.add_dependency 'coffee-rails' s.add_dependency 'formtastic', '~> 3.1' s.add_dependency 'formtastic_i18n' - s.add_dependency 'inherited_resources', '~> 1.4', '!= 1.5.0' + s.add_dependency 'inherited_resources', '~> 1.6' s.add_dependency 'jquery-rails' s.add_dependency 'jquery-ui-rails', '~> 5.0' s.add_dependency 'kaminari', '~> 0.15' diff --git a/app/assets/javascripts/active_admin/application.js.coffee b/app/assets/javascripts/active_admin/application.js.coffee index 8fa2a135bd0..0a19718c7bb 100644 --- a/app/assets/javascripts/active_admin/application.js.coffee +++ b/app/assets/javascripts/active_admin/application.js.coffee @@ -1,5 +1,5 @@ # Initializers -$ -> +$(document).on 'ready page:load', -> # jQuery datepickers (also evaluates dynamically added HTML) $(document).on 'focus', '.datepicker:not(.hasDatepicker)', -> defaults = dateFormat: 'yy-mm-dd' @@ -24,8 +24,8 @@ $ -> $('.filter_form_field.select_and_search select').change -> $(@).siblings('input').prop name: "q[#{@value}]" - # Tab navigation in the show page - $('#main_content .tabs').tabs() + # Tab navigation + $('#active_admin_content .tabs').tabs() # In order for index scopes to overflow properly onto the next line, we have # to manually set its width based on the width of the batch action button. diff --git a/app/assets/javascripts/active_admin/lib/batch_actions.js.coffee b/app/assets/javascripts/active_admin/lib/batch_actions.js.coffee index a6cf08e6d74..42013a07874 100644 --- a/app/assets/javascripts/active_admin/lib/batch_actions.js.coffee +++ b/app/assets/javascripts/active_admin/lib/batch_actions.js.coffee @@ -1,4 +1,4 @@ -$ -> +$(document).on 'ready page:load', -> # # Use ActiveAdmin.modal_dialog to prompt user if confirmation is required for current Batch Action diff --git a/app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee b/app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee index 5ab2dccefdf..57be96e4f78 100644 --- a/app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee +++ b/app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee @@ -1,7 +1,7 @@ class ActiveAdmin.CheckboxToggler constructor: (@options, @container)-> defaults = {} - @options = $.extend defaults, options + @options = $.extend defaults, @options @_init() @_bind() diff --git a/app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee b/app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee index e997fb6bb6a..446beb2debd 100644 --- a/app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee +++ b/app/assets/javascripts/active_admin/lib/dropdown-menu.js.coffee @@ -9,7 +9,7 @@ class ActiveAdmin.DropdownMenu onClickActionItemCallback: null } - @options = $.extend defaults, options + @options = $.extend defaults, @options @isOpen = false @$menuButton = @$element.find '.dropdown_menu_button' @@ -97,5 +97,5 @@ class ActiveAdmin.DropdownMenu $.widget.bridge 'aaDropdownMenu', ActiveAdmin.DropdownMenu -$ -> +$(document).on 'ready page:load', -> $('.dropdown_menu').aaDropdownMenu() diff --git a/app/assets/javascripts/active_admin/lib/per_page.js.coffee b/app/assets/javascripts/active_admin/lib/per_page.js.coffee new file mode 100644 index 00000000000..3bd29d2c341 --- /dev/null +++ b/app/assets/javascripts/active_admin/lib/per_page.js.coffee @@ -0,0 +1,27 @@ +class ActiveAdmin.PerPage + constructor: (@options, @element)-> + @$element = $(@element) + @_init() + @_bind() + + _init: -> + @$params = @_queryParams() + + _bind: -> + @$element.change => + @$params['per_page'] = @$element.val() + delete @$params['page'] + location.search = $.param(@$params) + + _queryParams: -> + query = window.location.search.substring(1) + params = {} + re = /([^&=]+)=([^&]*)/g + while m = re.exec(query) + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]) + params + +$.widget.bridge 'perPage', ActiveAdmin.PerPage + +$ -> + $('.pagination_per_page select').perPage() diff --git a/app/assets/stylesheets/active_admin/components/_pagination.scss b/app/assets/stylesheets/active_admin/components/_pagination.scss index f3fede99ef8..ad91730b128 100644 --- a/app/assets/stylesheets/active_admin/components/_pagination.scss +++ b/app/assets/stylesheets/active_admin/components/_pagination.scss @@ -32,3 +32,13 @@ .download_links { float: left; } + +.pagination_per_page { + float: right; + margin-left: 4px; + select { + @include light-button; + @include rounded(0px); + padding: 1px 5px; + } +} diff --git a/app/assets/stylesheets/active_admin/print.css.scss b/app/assets/stylesheets/active_admin/print.scss similarity index 100% rename from app/assets/stylesheets/active_admin/print.css.scss rename to app/assets/stylesheets/active_admin/print.scss diff --git a/docs/1-general-configuration.md b/docs/1-general-configuration.md index 5fda24c5933..4e55749406a 100644 --- a/docs/1-general-configuration.md +++ b/docs/1-general-configuration.md @@ -37,6 +37,7 @@ config.site_title = "My Admin Site" config.site_title_link = "/" config.site_title_image = "site_image.png" config.site_title_image = "http://www.google.com/images/logos/google_logo_41.png" +config.site_title_image = ->(context) { context.current_user.company.logo_url } ``` ## Internationalization (I18n) diff --git a/docs/6-show-pages.md b/docs/6-show-pages.md index 86401480e12..07c3777fcdc 100644 --- a/docs/6-show-pages.md +++ b/docs/6-show-pages.md @@ -33,7 +33,7 @@ ActiveAdmin.register Ad do show do attributes_table do row :title - row :image do + row :image do |ad| image_tag ad.image.url end end diff --git a/features/comments/commenting.feature b/features/comments/commenting.feature index b689d0a8e66..de011d8bca2 100644 --- a/features/comments/commenting.feature +++ b/features/comments/commenting.feature @@ -143,8 +143,6 @@ Feature: Commenting When I add a comment "Hello World" Then I should see a flash with "Comment was successfully created" And I should be in the resource section for publishers - When I am on the index page for comments - Then I should see the content "Publisher" And I should see "Hello World" Scenario: Commenting on a class with string id diff --git a/lib/active_admin/batch_actions/controller.rb b/lib/active_admin/batch_actions/controller.rb index aba8b2b3ffe..fe8b27105e1 100644 --- a/lib/active_admin/batch_actions/controller.rb +++ b/lib/active_admin/batch_actions/controller.rb @@ -5,9 +5,10 @@ module Controller # Controller action that is called when submitting the batch action form def batch_action if action_present? - selection = params[:collection_selection] || [] - inputs = JSON.parse params[:batch_action_inputs] || '{}' - inputs = inputs.with_indifferent_access.slice *current_batch_action.inputs.keys + selection = params[:collection_selection] || [] + inputs = JSON.parse params[:batch_action_inputs] || '{}' + valid_keys = render_in_context(self, current_batch_action.inputs).try(:keys) + inputs = inputs.with_indifferent_access.slice *valid_keys instance_exec selection, inputs, ¤t_batch_action.block else raise "Couldn't find batch action \"#{params[:batch_action]}\"" diff --git a/lib/active_admin/batch_actions/resource_extension.rb b/lib/active_admin/batch_actions/resource_extension.rb index f570ffce2d1..1a2904f679c 100644 --- a/lib/active_admin/batch_actions/resource_extension.rb +++ b/lib/active_admin/batch_actions/resource_extension.rb @@ -129,8 +129,7 @@ def confirm end def inputs - HashWithIndifferentAccess.new \ - @options[:form].is_a?(Proc) ? @options[:form].call : @options[:form] + @options[:form] end # Returns the display if block. If the block was not explicitly defined diff --git a/lib/active_admin/batch_actions/views/batch_action_selector.rb b/lib/active_admin/batch_actions/views/batch_action_selector.rb index 136cc06af25..cf370812141 100644 --- a/lib/active_admin/batch_actions/views/batch_action_selector.rb +++ b/lib/active_admin/batch_actions/views/batch_action_selector.rb @@ -33,7 +33,7 @@ def build_drop_down :class => "batch_action", "data-action" => batch_action.sym, "data-confirm" => confirmation_text, - "data-inputs" => batch_action.inputs.to_json + "data-inputs" => render_in_context(self, batch_action.inputs).to_json } default_title = render_or_call_method_or_proc_on(self, batch_action.title) diff --git a/lib/active_admin/namespace.rb b/lib/active_admin/namespace.rb index 6aa75a8ff0a..18796b2f6ee 100644 --- a/lib/active_admin/namespace.rb +++ b/lib/active_admin/namespace.rb @@ -146,8 +146,8 @@ def add_logout_button_to_menu(menu, priority = 20, html_options = {}) def add_current_user_to_menu(menu, priority = 10, html_options = {}) if current_user_method menu.add id: 'current_user', priority: priority, html_options: html_options, - label: ->{ display_name current_active_admin_user }, - url: ->{ auto_url_for(current_active_admin_user) || '#' }, + label: -> { display_name current_active_admin_user }, + url: -> { auto_url_for(current_active_admin_user) }, if: :current_active_admin_user? end end diff --git a/lib/active_admin/object_mapper/active_record/comments/comment.rb b/lib/active_admin/object_mapper/active_record/comments/comment.rb index d8d28587c71..22ef7f3f418 100644 --- a/lib/active_admin/object_mapper/active_record/comments/comment.rb +++ b/lib/active_admin/object_mapper/active_record/comments/comment.rb @@ -16,7 +16,7 @@ class Comment < ActiveRecord::Base # @return [String] The name of the record to use for the polymorphic relationship def self.resource_type(resource) - ResourceController::Decorators.undecorate(resource).class.name.to_s + ResourceController::Decorators.undecorate(resource).class.base_class.name.to_s end # Postgres adapters won't compare strings to numbers (issue 34) diff --git a/lib/active_admin/resource/routes.rb b/lib/active_admin/resource/routes.rb index 1ec928a8f7d..6330ed735d7 100644 --- a/lib/active_admin/resource/routes.rb +++ b/lib/active_admin/resource/routes.rb @@ -15,6 +15,10 @@ def route_instance_path(resource) RouteBuilder.new(self).instance_path(resource) end + def route_edit_instance_path(resource) + RouteBuilder.new(self).edit_instance_path(resource) + end + # Returns the routes prefix for this config def route_prefix namespace.module_name.try(:underscore) @@ -36,7 +40,7 @@ def initialize(resource) def collection_path(params) route_name = route_name( resource.resources_configuration[:self][:route_collection_name], - (resource.route_uncountable? ? 'index_path' : 'path') + suffix: (resource.route_uncountable? ? "index_path" : "path") ) routes.public_send route_name, *route_collection_params(params) @@ -51,13 +55,25 @@ def instance_path(instance) routes.public_send route_name, *route_instance_params(instance) end + # @return [String] the path to the edit page of this resource + # @param instance [ActiveRecord::Base] the instance we want the path of + # @example "/admin/posts/1/edit" + def edit_instance_path(instance) + path = resource.resources_configuration[:self][:route_instance_name] + route_name = route_name(path, action: :edit) + + routes.public_send route_name, *route_instance_params(instance) + end + private attr_reader :resource - def route_name(resource_path_name, suffix = 'path') + def route_name(resource_path_name, options = {}) + suffix = options[:suffix] || "path" route = [] + route << options[:action] # "edit" or "new" route << resource.route_prefix # "admin" route << belongs_to_name if nested? # "category" route << resource_path_name # "posts" or "post" diff --git a/lib/active_admin/resource_controller/data_access.rb b/lib/active_admin/resource_controller/data_access.rb index b8fc7706774..596f6a6ee73 100644 --- a/lib/active_admin/resource_controller/data_access.rb +++ b/lib/active_admin/resource_controller/data_access.rb @@ -276,12 +276,24 @@ def apply_pagination(chain) def per_page if active_admin_config.paginate - @per_page || active_admin_config.per_page + dynamic_per_page || configured_per_page else max_per_page end end + def dynamic_per_page + params[:per_page] || @per_page + end + + def configured_per_page + if active_admin_config.per_page.is_a?(Array) + active_admin_config.per_page[0] + else + active_admin_config.per_page + end + end + def max_per_page 10_000 end diff --git a/lib/active_admin/view_helpers/auto_link_helper.rb b/lib/active_admin/view_helpers/auto_link_helper.rb index 46fa618eb30..69935a6f6fd 100644 --- a/lib/active_admin/view_helpers/auto_link_helper.rb +++ b/lib/active_admin/view_helpers/auto_link_helper.rb @@ -21,8 +21,15 @@ def auto_link(resource, content = display_name(resource)) # Like `auto_link`, except that it only returns a URL instead of a full tag def auto_url_for(resource) - if config = active_admin_resource_for(resource.class) + config = active_admin_resource_for(resource.class) + return unless config + + if config.controller.action_methods.include?("show") && + authorized?(ActiveAdmin::Auth::READ, resource) url_for config.route_instance_path resource + elsif config.controller.action_methods.include?("edit") && + authorized?(ActiveAdmin::Auth::UPDATE, resource) + url_for config.route_edit_instance_path resource end end diff --git a/lib/active_admin/views/components/paginated_collection.rb b/lib/active_admin/views/components/paginated_collection.rb index dd07031add0..598ad14e2a1 100644 --- a/lib/active_admin/views/components/paginated_collection.rb +++ b/lib/active_admin/views/components/paginated_collection.rb @@ -40,6 +40,7 @@ def build(collection, options = {}) @param_name = options.delete(:param_name) @download_links = options.delete(:download_links) @display_total = options.delete(:pagination_total) { true } + @per_page = options.delete(:per_page) unless collection.respond_to?(:num_pages) raise(StandardError, "Collection is not a paginated scope. Set collection.page(params[:page]).per(10) before calling :paginated_collection.") @@ -63,6 +64,7 @@ def add_child(*args, &block) def build_pagination_with_formats(options) div id: "index_footer" do + build_per_page_select if @per_page.is_a?(Array) build_pagination div(page_entries_info(options).html_safe, class: "pagination_information") @@ -76,6 +78,21 @@ def build_pagination_with_formats(options) end end + def build_per_page_select + div class: "pagination_per_page" do + text_node "Per page:" + select do + @per_page.each do |per_page| + option( + per_page, + value: per_page, + selected: collection.limit_value == per_page ? "selected" : nil + ) + end + end + end + end + def build_pagination options = {} options[:param_name] = @param_name if @param_name diff --git a/lib/active_admin/views/pages/index.rb b/lib/active_admin/views/pages/index.rb index a53427ab925..749b2f26459 100644 --- a/lib/active_admin/views/pages/index.rb +++ b/lib/active_admin/views/pages/index.rb @@ -127,11 +127,13 @@ def render_index paginator = config.fetch(:paginator, true) download_links = config.fetch(:download_links, active_admin_config.namespace.download_links) pagination_total = config.fetch(:pagination_total, true) + per_page = config.fetch(:per_page, active_admin_config.per_page) paginated_collection(collection, entry_name: active_admin_config.resource_label, entries_name: active_admin_config.plural_resource_label(count: collection_size), download_links: download_links, paginator: paginator, + per_page: per_page, pagination_total: pagination_total) do div class: 'index_content' do insert_tag(renderer_class, config, collection) @@ -159,4 +161,3 @@ def default_blank_slate_link end end end - diff --git a/lib/active_admin/views/tabbed_navigation.rb b/lib/active_admin/views/tabbed_navigation.rb index 9b5b18f7fef..f72c838d623 100644 --- a/lib/active_admin/views/tabbed_navigation.rb +++ b/lib/active_admin/views/tabbed_navigation.rb @@ -43,7 +43,11 @@ def build_menu_item(item) li id: item.id do |li| li.add_class "current" if item.current? assigns[:current_tab] - text_node link_to item.label(self), item.url(self), item.html_options + if url = item.url(self) + text_node link_to item.label(self), url, item.html_options + else + span item.label(self), item.html_options + end if children = item.items(self).presence li.add_class "has_nested" diff --git a/spec/unit/auto_link_spec.rb b/spec/unit/auto_link_spec.rb index f34cd992f25..822efc0b1eb 100644 --- a/spec/unit/auto_link_spec.rb +++ b/spec/unit/auto_link_spec.rb @@ -14,6 +14,10 @@ def admin_post_path(post) "/admin/posts/#{post.id}" end + def authorized?(_action, _subject) + true + end + context "when the resource is not registered" do it "should return the display name of the object" do expect(auto_link(post)).to eq "Hello World" @@ -25,10 +29,41 @@ def admin_post_path(post) active_admin_namespace.register Post end it "should return a link with the display name of the object" do - expect(self).to receive(:url_for).and_return admin_post_path(post) + expect(self).to receive(:url_for) { |url| url } expect(self).to receive(:link_to).with "Hello World", admin_post_path(post) auto_link(post) end - end + context "but the user doesn't have access" do + it "should return the display name of the object" do + expect(self).to receive(:authorized?).twice.and_return(false) + expect(auto_link(post)).to eq "Hello World" + end + end + + context "but the show action is disabled" do + before do + active_admin_namespace.register(Post) { actions :all, except: :show } + end + + it "should fallback to edit" do + url_path = "/admin/posts/#{post.id}/edit" + expect(self).to receive(:url_for) { |url| url } + expect(self).to receive(:link_to).with "Hello World", url_path + auto_link(post) + end + end + + context "but the show and edit actions are disabled" do + before do + active_admin_namespace.register(Post) do + actions :all, except: [:show, :edit] + end + end + + it "should return the display name of the object" do + expect(auto_link(post)).to eq "Hello World" + end + end + end end diff --git a/spec/unit/comments_spec.rb b/spec/unit/comments_spec.rb index d20d067bc96..beb66552b79 100644 --- a/spec/unit/comments_spec.rb +++ b/spec/unit/comments_spec.rb @@ -126,7 +126,7 @@ namespace: namespace_name) expect(ActiveAdmin::Comment.find_for_resource_in_namespace(publisher, namespace_name).last.resource_type). - to eq('Publisher') + to eq("User") end end end diff --git a/spec/unit/resource_controller_spec.rb b/spec/unit/resource_controller_spec.rb index b77d8add7b7..46bf7d650b9 100644 --- a/spec/unit/resource_controller_spec.rb +++ b/spec/unit/resource_controller_spec.rb @@ -232,11 +232,19 @@ def call_after_destroy(obj); end end describe "when params batch_action matches existing BatchAction" do - it "should call the block with args" do + before do allow(controller).to receive(:params) { { batch_action: "flag", collection_selection: ["1"] } } + end + + it "should call the block with args" do expect(controller).to receive(:instance_exec).with(["1"], {}) controller.batch_action end + + it "should call the block in controller scope" do + expect(controller).to receive(:render_in_context).with(controller, nil).and_return({}) + controller.batch_action + end end describe "when params batch_action doesn't match a BatchAction" do diff --git a/spec/unit/views/components/paginated_collection_spec.rb b/spec/unit/views/components/paginated_collection_spec.rb index 073ef78d13e..30f2e192e2e 100644 --- a/spec/unit/views/components/paginated_collection_spec.rb +++ b/spec/unit/views/components/paginated_collection_spec.rb @@ -226,5 +226,21 @@ def paginated_collection(*args) end end + context "when specifying per_page: array option" do + let(:collection) do + posts = 10.times.map { Post.new } + Kaminari.paginate_array(posts).page(1).per(5) + end + + let(:pagination) { paginated_collection(collection, per_page: [1, 2, 3]) } + let(:pagination_html) { pagination.find_by_class("pagination_per_page").first } + let(:pagination_node) { Capybara.string(pagination_html.to_s) } + + it "should render per_page select tag" do + expect(pagination_html.content).to match(/Per page:/) + expect(pagination_node).to have_css("select option", count: 3) + end + end + end end diff --git a/spec/unit/views/tabbed_navigation_spec.rb b/spec/unit/views/tabbed_navigation_spec.rb index f2cf7a838e3..e52556fbd08 100644 --- a/spec/unit/views/tabbed_navigation_spec.rb +++ b/spec/unit/views/tabbed_navigation_spec.rb @@ -48,6 +48,8 @@ priority: 10, if: :admin_logged_in? end + + menu.add label: "Charles Smith", id: "current_user", url: -> { nil } end it "should generate a ul" do @@ -95,6 +97,13 @@ expect(html).to_not have_selector("a[href='#']", text: "Management") end + context "when url is nil" do + it "should generate a span" do + selector = "li#current_user > span" + expect(html).to have_selector(selector, text: "Charles Smith") + end + end + describe "marking current item" do it "should add the 'current' class to the li" do