├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── active_admin-duplicatable.gemspec ├── lib └── active_admin │ ├── duplicatable.rb │ └── duplicatable │ └── version.rb └── spec ├── duplicatable_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .rspec 2 | .bundle/ 3 | log/*.log 4 | pkg/ 5 | Gemfile.lock 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.4.0] - 2018-07-18 8 | 9 | ### Changed 10 | 11 | - Switch from Rails' `before_filter` to `before_action`. The method 12 | `before_filter` was deprecated in Rails 5.0 and removed in Rails 5.1. 13 | 14 | ## [0.3.0] - 2017-10-03 15 | 16 | ### Added 17 | 18 | - Allow custom duplication methods in the `:via` option. Rather than always 19 | calling `#amoeba_dup`, it can be configured to call any method. 20 | 21 | ## [0.2.0] - 2015-07-23 22 | 23 | - Avoid ActiveAdmin `1.0.0.pre1` deprecation warning when `#action_item` isn't 24 | named, and maintain backward compatibility with older ActiveAdmin versions. 25 | 26 | ## [0.1.0] - 2014-12-15 27 | 28 | ### Added 29 | 30 | - Check user has permission to create resources in duplciation action and when 31 | displaying the "Duplicate Model" action item. 32 | 33 | ## 0.0.1 - 2014-06-02 34 | 35 | - Initial release 36 | 37 | [unreleased]: https://github.com/zorab47/active_admin-duplicatable/compare/v0.3.0...HEAD 38 | [0.1.0]: https://github.com/zorab47/active_admin-duplicatable/compare/v0.0.1...v0.1.0 39 | [0.2.0]: https://github.com/zorab47/active_admin-duplicatable/compare/v0.1.0...v0.2.0 40 | [0.3.0]: https://github.com/zorab47/active_admin-duplicatable/compare/v0.2.0...v0.3.0 41 | [0.4.0]: https://github.com/zorab47/active_admin-duplicatable/compare/v0.3.0...v0.4.0 42 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in activeadmin-subnav.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Charles Maresh 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActiveAdmin::Duplicatable 2 | 3 | [![Gem Version](https://badge.fury.io/rb/active_admin-duplicatable.svg)](http://badge.fury.io/rb/active_admin-duplicatable) 4 | 5 | ## Description 6 | 7 | Simple duplication of [ActiveAdmin][] resources. 8 | 9 | Allow user duplication of [ActiveAdmin][] resources through the power of the 10 | [Amoeba][] gem. Resource duplication can be performed by either persisting the 11 | model and its relations, or by pre-filling a new resource's form fields with 12 | defaults. 13 | 14 | ## Usage 15 | 16 | Add duplication functionality to a Post resource. See [Duplicatable][] for 17 | documentation. 18 | 19 | ```ruby 20 | ActiveAdmin.register Post do 21 | duplicatable 22 | end 23 | ``` 24 | 25 | ## Installation 26 | 27 | Add this line to your application's Gemfile: 28 | 29 | ```ruby 30 | gem 'active_admin-duplicatable' 31 | ``` 32 | 33 | And then execute: 34 | 35 | $ bundle 36 | 37 | ## Contributing 38 | 39 | 1. Fork it ( https://github.com/zorab47/active_admin-duplicatable/fork ) 40 | 2. Create your feature branch (`git checkout -b my-new-feature`) 41 | 3. Commit your changes (`git commit -am 'Add some feature'`) 42 | 4. Push to the branch (`git push origin my-new-feature`) 43 | 5. Create a new Pull Request 44 | 45 | ## Versioning 46 | 47 | Follows [Semantic Versioning 2.0.0][Semver] 48 | 49 | [ActiveAdmin]: https://github.com/gregbell/active_admin 50 | [Amoeba]: https://github.com/rocksolidwebdesign/amoeba 51 | [Duplicatable]: lib/active_admin/duplicatable.rb 52 | [Semver]: http://semver.org/spec/v2.0.0.html 53 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /active_admin-duplicatable.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'active_admin/duplicatable/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "active_admin-duplicatable" 8 | spec.version = Activeadmin::Duplicatable::VERSION 9 | spec.authors = ["Charles Maresh"] 10 | spec.email = ["charles.maresh@orm-tech.com"] 11 | spec.summary = %q{Simple duplication of ActiveAdmin resources} 12 | spec.description = <= 2.0.0" 27 | 28 | spec.add_development_dependency "bundler", "~> 1.6" 29 | spec.add_development_dependency "rake" 30 | spec.add_development_dependency "rspec" 31 | spec.add_development_dependency "activerecord", "~> 3.2.18" 32 | 33 | # required for active admin 34 | spec.add_development_dependency "sass-rails" 35 | end 36 | -------------------------------------------------------------------------------- /lib/active_admin/duplicatable.rb: -------------------------------------------------------------------------------- 1 | require "active_admin" 2 | require "active_admin/duplicatable/version" 3 | 4 | module ActiveAdmin 5 | module Duplicatable 6 | 7 | extend ActiveSupport::Concern 8 | 9 | # Public: Enable and configure resource duplication 10 | # 11 | # options - Duplication options (default: { via: :form }): 12 | # :via - Method of duplication. Via `:save` is the only way to 13 | # copy a resources relations not present in the form. 14 | # 15 | # Examples 16 | # 17 | # ActiveAdmin.register Post do 18 | # duplicatable via: :save 19 | # end 20 | # 21 | def duplicatable(options = {}) 22 | via = options.fetch(:via) { :form } 23 | 24 | if via == :save 25 | enable_resource_duplication_via_save 26 | elsif via != :form 27 | enable_resource_duplication_via_custom_method(via) 28 | else 29 | enable_resource_duplication_via_form 30 | end 31 | end 32 | 33 | private 34 | 35 | # Enables resource duplication via new form. 36 | # 37 | # - Adds a duplicate action button. 38 | # - Preloads a duplicated resource on `:new` to pre-fill the form fields. 39 | # 40 | # No return. 41 | def enable_resource_duplication_via_form 42 | action_item(*compatible_action_item_parameters) do 43 | if controller.action_methods.include?('new') && authorized?(ActiveAdmin::Auth::CREATE, active_admin_config.resource_class) 44 | link_to(I18n.t(:duplicate_model, default: "Duplicate %{model}", scope: [:active_admin], model: active_admin_config.resource_label), action: :new, _source_id: resource.id) 45 | end 46 | end 47 | 48 | controller do 49 | before_action only: :new do 50 | if !params[:_source_id].blank? 51 | source = resource_class.find(params[:_source_id]) 52 | @resource ||= source.amoeba_dup if source 53 | end 54 | end 55 | end 56 | end 57 | 58 | # Enables resource duplication via save. 59 | # 60 | # - Adds a duplicate action button. 61 | # - Duplicates a resource, persists it, and redirects the user to edit 62 | # the newly duplicated resource. 63 | # 64 | # No return. 65 | def enable_resource_duplication_via_save 66 | action_item(*compatible_action_item_parameters) do 67 | if controller.action_methods.include?('new') && authorized?(ActiveAdmin::Auth::CREATE, active_admin_config.resource_class) 68 | link_to(I18n.t(:duplicate_model, default: "Duplicate %{model}", scope: [:active_admin], model: active_admin_config.resource_label), action: :duplicate) 69 | end 70 | end 71 | 72 | member_action :duplicate do 73 | resource = resource_class.find(params[:id]) 74 | 75 | authorize! ActiveAdmin::Auth::CREATE, resource 76 | 77 | duplicate = resource.amoeba_dup 78 | if duplicate.save 79 | redirect_to({ action: :edit, id: duplicate.id }, flash: { notice: "#{active_admin_config.resource_label} was successfully duplicated." }) 80 | else 81 | redirect_to({ action: :show }, flash: { error: "#{active_admin_config.resource_label} could not be duplicated." }) 82 | end 83 | end 84 | end 85 | 86 | # Enables resource duplication via a custom method 87 | # 88 | # - Adds a duplicate action button. 89 | # - Calls a custom duplication method on the model. The method should 90 | # handle any copying of data and persistence of the new record. 91 | # - Redirects the user to edit the newly duplicated resource. 92 | # 93 | # No return. 94 | def enable_resource_duplication_via_custom_method(method) 95 | action_item(*compatible_action_item_parameters) do 96 | if controller.action_methods.include?('new') && authorized?(ActiveAdmin::Auth::CREATE, active_admin_config.resource_class) 97 | link_to(I18n.t(:duplicate_model, default: "Duplicate %{model}", scope: [:active_admin], model: active_admin_config.resource_label), action: :duplicate) 98 | end 99 | end 100 | 101 | member_action :duplicate do 102 | resource = resource_class.find(params[:id]) 103 | 104 | authorize! ActiveAdmin::Auth::CREATE, resource 105 | 106 | begin 107 | duplicate = resource.send method 108 | redirect_to({ action: :edit, id: duplicate.id }, flash: { notice: "#{active_admin_config.resource_label} was successfully duplicated." }) 109 | rescue => e 110 | Rails.logger.warn(e) 111 | redirect_to({ action: :show }, flash: { error: "#{active_admin_config.resource_label} could not be duplicated." }) 112 | end 113 | end 114 | end 115 | 116 | # For ActiveAdmin `action_item` compatibility. 117 | # 118 | # - When ActiveAdmin is less than 1.0.0.pre1 exclude name parameter from 119 | # calls to `action_item` for compatibility. 120 | # - When 1.0.0.pre1 or greater provide name to `action_item` to avoid the 121 | # warning message, and later an error. 122 | # 123 | # Returns Array of parameters. 124 | def compatible_action_item_parameters 125 | parameters = [{ :only => [:show] }] 126 | parameters.unshift(:duplicatable_duplicate) if action_item_name_required? 127 | parameters 128 | end 129 | 130 | def action_item_name_required? 131 | method(:action_item).parameters.count == 3 132 | end 133 | end 134 | end 135 | 136 | ActiveAdmin::ResourceDSL.send :include, ActiveAdmin::Duplicatable 137 | -------------------------------------------------------------------------------- /lib/active_admin/duplicatable/version.rb: -------------------------------------------------------------------------------- 1 | module Activeadmin 2 | module Duplicatable 3 | VERSION = "0.4.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/duplicatable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveAdmin::Duplicatable do 4 | 5 | describe "extends ResourceDSL" do 6 | it "by adding #duplicatable" do 7 | dsl = ActiveAdmin::ResourceDSL 8 | 9 | expect(dsl.public_instance_methods).to include(:duplicatable) 10 | end 11 | end 12 | 13 | it "enables form-based duplication by default" do 14 | dsl = ActiveAdmin::ResourceDSL.new(double 'config') 15 | 16 | expect(dsl).to receive(:enable_resource_duplication_via_form) 17 | 18 | dsl.duplicatable 19 | end 20 | 21 | it 'enables save-based duplication with option `via: :save`' do 22 | dsl = ActiveAdmin::ResourceDSL.new(double 'config') 23 | 24 | expect(dsl).to receive(:enable_resource_duplication_via_save) 25 | 26 | dsl.duplicatable(via: :save) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'active_admin/duplicatable' 3 | 4 | RSpec.configure do |config| 5 | # These two settings work together to allow you to limit a spec run 6 | # to individual examples or groups you care about by tagging them with 7 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 8 | # get run. 9 | config.filter_run :focus 10 | config.run_all_when_everything_filtered = true 11 | 12 | # Many RSpec users commonly either run the entire suite or an individual 13 | # file, and it's useful to allow more verbose output when running an 14 | # individual spec file. 15 | if config.files_to_run.one? 16 | # Use the documentation formatter for detailed output, 17 | # unless a formatter has already been configured 18 | # (e.g. via a command-line flag). 19 | config.default_formatter = 'doc' 20 | end 21 | 22 | # Run specs in random order to surface order dependencies. If you find an 23 | # order dependency and want to debug it, you can fix the order by providing 24 | # the seed, which is printed after each run. 25 | # --seed 1234 26 | config.order = :random 27 | 28 | # Seed global randomization in this process using the `--seed` CLI option. 29 | # Setting this allows you to use `--seed` to deterministically reproduce 30 | # test failures related to randomization by passing the same `--seed` value 31 | # as the one that triggered the failure. 32 | Kernel.srand config.seed 33 | 34 | config.expect_with :rspec do |expectations| 35 | expectations.syntax = :expect 36 | end 37 | 38 | config.mock_with :rspec do |mocks| 39 | mocks.syntax = :expect 40 | mocks.verify_partial_doubles = true 41 | end 42 | end 43 | --------------------------------------------------------------------------------