activeadmin_polymorphic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/Gemfile +40 -0
  4. data/Guardfile +8 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +86 -0
  7. data/Rakefile +33 -0
  8. data/activeadmin_polymorphic.gemspec +19 -0
  9. data/app/assets/javascripts/activeadmin_polymorphic.js.coffee +172 -0
  10. data/app/assets/stylesheets/activeadmin_polymorphic.css.sass +25 -0
  11. data/lib/activeadmin_polymorphic/engine.rb +5 -0
  12. data/lib/activeadmin_polymorphic/form_builder.rb +124 -0
  13. data/lib/activeadmin_polymorphic/version.rb +3 -0
  14. data/lib/activeadmin_polymorphic.rb +5 -0
  15. data/spec/rails_helper.rb +154 -0
  16. data/spec/spec_helper.rb +17 -0
  17. data/spec/support/deferred_garbage_collection.rb +19 -0
  18. data/spec/support/detect_rails_version.rb +26 -0
  19. data/spec/support/integration_example_group.rb +31 -0
  20. data/spec/support/jslint.yml +80 -0
  21. data/spec/support/rails_template.rb +104 -0
  22. data/spec/support/rails_template_with_data.rb +59 -0
  23. data/spec/support/templates/cucumber.rb +24 -0
  24. data/spec/support/templates/cucumber_with_reloading.rb +5 -0
  25. data/spec/support/templates/en.yml +8 -0
  26. data/spec/support/templates/policies/active_admin/comment_policy.rb +9 -0
  27. data/spec/support/templates/policies/active_admin/page_policy.rb +18 -0
  28. data/spec/support/templates/policies/application_policy.rb +45 -0
  29. data/spec/support/templates/policies/category_policy.rb +7 -0
  30. data/spec/support/templates/policies/post_policy.rb +15 -0
  31. data/spec/support/templates/policies/store_policy.rb +11 -0
  32. data/spec/support/templates/policies/user_policy.rb +11 -0
  33. data/spec/support/templates/post_decorator.rb +11 -0
  34. data/spec/unit/form_builder_spec.rb +121 -0
  35. data/tasks/test.rake +83 -0
  36. metadata +102 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 806875f4dc043b24f94549838a81c5ad6d180e4b
4
+ data.tar.gz: 786835f0298f3ce1e08870a9bede6f9ab036d9e6
5
+ SHA512:
6
+ metadata.gz: 8a5da9426cf627abefc48134e70fc9bea72ebd90de1f8c1e28094e6dc693037609120a1ed514a26a1d5b417ed64a6d558623f8a47ffcab26b0585eee6b60b2e5
7
+ data.tar.gz: d8f296a37a267d68ccccf8d9cfea1b8449f7ebe66b3a889e1791ad29126093191a68bde27f27c8834cb093d4aa64a03bd8eff151f36b3c0e2a65bede0e26e666
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ ## Mac
3
+ .DS_Store
4
+
5
+ ## Windows
6
+ .Thumbs.db
7
+
8
+ ## TextMate
9
+ *.tm_project
10
+ *.tmproj
11
+ tmtags
12
+
13
+ ## Emacs
14
+ *~
15
+ \#*
16
+ .\#*
17
+
18
+ ## Vim
19
+ *.swp
20
+ # IDEA / RUBYMINE
21
+ .idea
22
+
23
+ ## RVM
24
+ .rvmrc
25
+ .ruby-version
26
+ .ruby-gemset
27
+
28
+ ## Project (general)
29
+ tags
30
+ coverage
31
+ rdoc
32
+ doc
33
+ .yardoc
34
+ pkg
35
+
36
+ ## Project (specific)
37
+ bin/
38
+ .bundle
39
+ spec/rails
40
+ *.sqlite3-journal
41
+ Gemfile.lock
42
+ Gemfile-*.lock
43
+ capybara*
44
+ viewcumber
45
+ test-rails*
46
+ public
47
+ .rspec
48
+ .rails-version
49
+ .rbenv-version
50
+ .localeapp/*
data/Gemfile ADDED
@@ -0,0 +1,40 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'activeadmin', github: 'activeadmin'
6
+ gem 'inherited_resources'
7
+
8
+ group :development do
9
+ # Debugging
10
+ gem 'better_errors' # Web UI to debug exceptions. Go to /__better_errors to access the latest one
11
+ gem 'binding_of_caller' # Retrieve the binding of a method's caller in MRI Ruby >= 1.9.2
12
+
13
+ # Performance
14
+ gem 'rack-mini-profiler' # Inline app profiler. See ?pp=help for options.
15
+ gem 'flamegraph' # Flamegraph visualiztion: ?pp=flamegraph
16
+
17
+ # Documentation
18
+ gem 'yard' # Documentation generator
19
+ gem 'redcarpet' # Markdown implementation (for yard)
20
+ end
21
+
22
+ group :test do
23
+ gem 'capybara'
24
+ gem 'simplecov', require: false # Test coverage generator. Go to /coverage/ after running tests
25
+ gem 'coveralls', require: false # Test coverage website. Go to https://coveralls.io
26
+ gem 'cucumber-rails', require: false
27
+ gem 'database_cleaner'
28
+ gem 'guard-rspec'
29
+ gem 'jasmine'
30
+ gem 'jslint_on_rails'
31
+ gem 'launchy'
32
+ gem 'rails-i18n' # Provides default i18n for many languages
33
+ gem 'rspec'
34
+ gem 'rspec-mocks'
35
+ gem 'rspec-rails'
36
+ gem 'i18n-spec'
37
+ gem 'shoulda-matchers'
38
+ gem 'sqlite3'
39
+ gem 'poltergeist'
40
+ end
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # More info at https://github.com/guard/guard#readme
2
+
3
+ guard 'rspec', all_on_start: false, cmd: "bundle exec rspec" do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/active_admin_polymorphic/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
6
+ watch('spec/spec_helper.rb') { "spec/" }
7
+ watch('spec/rails_helper.rb') { "spec/" }
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Sergeev Peter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # ActiveadminPolymorphic
2
+
3
+ Activeadmin Polymorphic gem is made to bring has_many polymorphic nested forms into your ActiveAdmin. ActiveAdmin users formtastic to build awesome forms, but formatstic itself doesn't support polymorphic relations. `activeadmin_polymorphic` gem is trying to solve that problem.
4
+
5
+ ![](https://s3.amazonaws.com/f.cl.ly/items/0b2F2t2R3D0o1O3F1R3e/Screen%20Shot%202015-02-05%20at%2012.57.04.png)
6
+
7
+ # Features
8
+
9
+ * polymorphic forms
10
+ * validation
11
+ * sortable behaviour
12
+ * file uploads
13
+
14
+ # Installation
15
+
16
+ Add this to your Gemfile:
17
+
18
+ ``` ruby
19
+ gem "activeadmin_polymorphic"
20
+ ```
21
+
22
+ and run `bundle install`.
23
+
24
+ Include assets in js and css manifests
25
+
26
+ ```
27
+ #= require activeadmin_polymorphic
28
+ @import "activeadmin_polymorphic";
29
+ ```
30
+
31
+ # Usage
32
+
33
+ To use gem, your model should have related model, which works as a proxy to polymorphic relations.
34
+
35
+ ![](https://s3.amazonaws.com/f.cl.ly/items/2Z3M2V0b3Z342L2Z2R0N/Screen%20Shot%202015-02-05%20at%2013.37.36.png)
36
+
37
+ Gem extrands activeadmin's form builder, so to enable `has_many_polymorphic` method you need to override form builder using `builder` option:
38
+
39
+ ```
40
+ ...
41
+ SECTIONABLES = [Image, Text]
42
+
43
+ form builder: ActiveadminPolymorphic::FormBuilder do |f|
44
+ f.polymorphic_has_many :sections, :sectionable, types: SECTIONABLES
45
+ end
46
+ ...
47
+ ```
48
+
49
+ There are few options available:
50
+ * first option is a name of polymorphic has_many association
51
+ * second option referes to polymorphied version of association name (sectionable_id and sectaionable_type for example)
52
+ * `types` - list of related models you want to use
53
+ * `allow_destroy` - weather or not to allow to destroy related objects
54
+ * `sortable` - enables drag'n'drop for nested forms, accepts sortable column name, for example `sortable: :priority`
55
+
56
+ Subforms for polymorphic relations are forms which you define in your ActiveAdmin. Gem fetches and submits them using some ajax magic.
57
+
58
+ # Under the hood
59
+
60
+ This gem is a set of dirty hacks and tricks. Calling `polymorphic_has_many` makes it to do the following things:
61
+
62
+ * for new records it generates dropdown with polymorphic types
63
+ * for exising records it generates two hidden fields with id and type
64
+ * then the real javascript starts, it extracts whole forms from polymorphic models new or edit pages, strips form actions, and inserts that forms right into parent form
65
+ * when you try to submit forms, javascripts submit subforms first; if subforms are invalid, it reloads them with erros and interupts main form submission process
66
+ * after all sub forms successfully saved, it strips them (because forms nested into other forms are simantically invalid, right?) and submits parent form
67
+
68
+ # File uploads in subforms
69
+
70
+ Gem relies on [rails ajax](https://github.com/rails/jquery-rails) form submissions, which doesn't allow to submit files directly. Workaround for it is asynchronous file submission using for example [remotipart](https://github.com/JangoSteve/remotipart) for CarrierWave or [refile](https://github.com/elabs/refile) with [refile-input](https://github.com/hyperoslo/refile-input). Note: before subforms submissions javascript strips all file inputs from forms.
71
+
72
+ # Testing
73
+
74
+ Tests stucture is mostly copied from original ActiveAdmin.
75
+
76
+ Install development dependencies with `bundle install`. To setup test suit run `rake test`. Run tests with `bundle exec guard`. There aren't many of them, let's say there are quite a few.
77
+
78
+ # In plan
79
+
80
+ * allow to reuse existing polymorphic objects
81
+ * check who it works with models under sertain namespace
82
+ * improve tests
83
+
84
+ # License
85
+
86
+ [MIT](LICENSE.txt)
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'bundler'
2
+ require 'rake'
3
+ Bundler.setup
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ def cmd(command)
7
+ puts command
8
+ fail unless system command
9
+ end
10
+
11
+ require File.expand_path('../spec/support/detect_rails_version', __FILE__)
12
+
13
+ # Import all our rake tasks
14
+ FileList['tasks/**/*.rake'].each { |task| import task }
15
+
16
+ task default: :test
17
+
18
+ begin
19
+ require 'jasmine'
20
+ load 'jasmine/tasks/jasmine.rake'
21
+ rescue LoadError
22
+ task :jasmine do
23
+ abort 'Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine'
24
+ end
25
+ end
26
+
27
+ task :console do
28
+ require 'irb'
29
+ require 'irb/completion'
30
+
31
+ ARGV.clear
32
+ IRB.start
33
+ end
@@ -0,0 +1,19 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "activeadmin_polymorphic/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |s|
8
+ s.license = "MIT"
9
+ s.name = "activeadmin_polymorphic"
10
+ s.version = ActiveadminPolymorphic::VERSION
11
+ s.authors = ["Petr Sergeev", "Sindre Moen"]
12
+ s.email = ["peter@hyper.no", "sindre@hyper.no"]
13
+ s.description = 'This gem extends formtastic\'s form builder to support polymoprhic has many relations in your forms'
14
+ s.summary = 'HasMany polymoprhic support for active admin.'
15
+ s.homepage = "https://github.com/hyperoslo/activeadmin-polymorphic"
16
+
17
+ s.files = `git ls-files`.split("\n").sort
18
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
19
+ end
@@ -0,0 +1,172 @@
1
+ $ ->
2
+ if $('.polymorphic_has_many_container').length
3
+ form = $('#main_content').find('form:first')
4
+ $(form).on 'submit', (e) ->
5
+ submissions_counter = 0
6
+ parentForm = @
7
+ expect = $(@).find('form').length
8
+ if submissions_counter < expect
9
+ e.preventDefault()
10
+
11
+ $(@).find('form').each ->
12
+ remoteSubmit @, ->
13
+ submissions_counter++
14
+ if submissions_counter == expect
15
+ $(form).find('form').remove()
16
+ stripEmptyRelations()
17
+ $(parentForm).submit()
18
+
19
+ $(document).on "upload:start", "form", (event) ->
20
+ form = $('#main_content').find('form:first')
21
+ form.find("input[type=submit]").attr "disabled", true
22
+
23
+ $(document).on "upload:complete", "form", (event) ->
24
+ form = $('#main_content').find('form:first')
25
+
26
+ unless form.find("input.uploading").length
27
+ form.find("input[type=submit]").removeAttr "disabled"
28
+
29
+ $('.polymorphic_has_many_fields').each (index, rapper) ->
30
+ rapper = $ rapper
31
+
32
+ hiddenField = rapper.find 'input[type=hidden][data-path]'
33
+ formPath = hiddenField.data 'path'
34
+
35
+ extractAndInsertForm formPath, rapper
36
+
37
+
38
+ $(document).on 'click', 'a.button.polymorphic_has_many_remove', (e)->
39
+ e.preventDefault()
40
+ parent = $(@).closest '.polymorphic_has_many_container'
41
+ to_remove = $(@).closest 'fieldset'
42
+ recompute_positions parent
43
+
44
+ parent.trigger 'polymorphic_has_many_remove:before', [to_remove, parent]
45
+ to_remove.remove()
46
+ parent.trigger 'polymorphic_has_many_remove:after', [to_remove, parent]
47
+
48
+ $(document).on 'click', 'a.button.polymorphic_has_many_add', (e)->
49
+ e.preventDefault()
50
+ parent = $(@).closest '.polymorphic_has_many_container'
51
+ parent.trigger before_add = $.Event('polymorphic_has_many_add:before'), [parent]
52
+
53
+ unless before_add.isDefaultPrevented()
54
+ index = parent.data('polymorphic_has_many_index') || parent.children('fieldset').length - 1
55
+ parent.data has_many_index: ++index
56
+
57
+ regex = new RegExp $(@).data('placeholder'), 'g'
58
+ html = $(@).data('html').replace regex, index
59
+
60
+ fieldset = $(html).insertBefore(@)
61
+ recompute_positions parent
62
+ parent.trigger 'polymorphic_has_many_add:after', [fieldset, parent]
63
+
64
+ init_polymorphic_sortable()
65
+
66
+
67
+ $('.polymorphic_has_many_container').on 'change', '.polymorphic_type_select', (event) ->
68
+ fieldset = $(this).closest 'fieldset'
69
+
70
+ selectedOption = $(this).find 'option:selected'
71
+ formPath = selectedOption.data 'path'
72
+
73
+ label = $(this).prev 'label'
74
+ label.remove()
75
+
76
+ hiddenField = $('<input type="hidden" />')
77
+ hiddenField.attr 'name', $(this).attr('name')
78
+ hiddenField.val $(this).val()
79
+
80
+ $(this).replaceWith hiddenField
81
+
82
+ newListItem = $ '<li>'
83
+
84
+ extractAndInsertForm formPath, fieldset
85
+
86
+ init_polymorphic_sortable = ->
87
+ elems = $('.polymorphic_has_many_container[data-sortable]:not(.ui-sortable)')
88
+
89
+ elems.sortable
90
+ axis: 'y'
91
+ items: '> fieldset',
92
+ handle: '> ol > .handle',
93
+ stop: recompute_positions
94
+ elems.each recompute_positions
95
+
96
+ # Removes relations if id or type is not specified
97
+ # For example when user clicked add relation button, but didn't selected type
98
+ stripEmptyRelations = ->
99
+ $('.polymorphic_has_many_fields input:hidden').each ->
100
+ if $(@).val() == ""
101
+ $(@).parents('.polymorphic_has_many_fields').remove()
102
+
103
+ recompute_positions = (parent)->
104
+ parent = if parent instanceof jQuery then parent else $(@)
105
+ input_name = parent.data 'sortable'
106
+ position = parseInt(parent.data('sortable-start') || 0, 10)
107
+
108
+ parent.children('fieldset').each ->
109
+ # We ignore nested inputs, so when defining your has_many, be sure to keep
110
+ # your sortable input at the root of the has_many block.
111
+ destroy_input = $(@).find "> ol > .input > :input[name$='[_destroy]']"
112
+ sortable_input = $(@).find "> ol > .input > :input[name$='[#{input_name}]']"
113
+
114
+ if sortable_input.length
115
+ sortable_input.val if destroy_input.is ':checked' then '' else position++
116
+
117
+ window.extractAndInsertForm= (url, target)->
118
+ target = $ target
119
+
120
+ $.get url, (data)->
121
+ elements = $(data)
122
+ form = $('#main_content form', elements).first()
123
+ $(form).find('.actions').remove()
124
+ $(form).on 'submit', -> return false
125
+
126
+ target.prepend form
127
+
128
+ window.loadErrors = (target) ->
129
+ $(target).off('ajax:success') # unbind successfull action for json form
130
+ $(target).trigger('submit.rails').on 'ajax:success', (event, data, result) ->
131
+ # duplicates method above. refactor using callbacks
132
+ elements = $(data)
133
+ form = $('#main_content form', elements).first()
134
+ $(form).find('.actions').remove()
135
+ $(form).on 'submit', -> return false
136
+
137
+ $(target).replaceWith(form)
138
+
139
+
140
+ window.remoteSubmit = (target, callback)->
141
+ $(target).data('remote', true)
142
+ $(target).removeAttr('novalidate')
143
+ action = $(target).attr('action')
144
+ $(target).find("input[type=file]").remove()
145
+ # we gonna burn in hell for that
146
+ # perhaps we can use ajax:before callback
147
+ # to set type json
148
+ action_with_json = action + '.json'
149
+ $(target).attr('action', action_with_json)
150
+
151
+ # unbind callbacks action for form if it was submitted before
152
+ $(target).off('ajax:success').off('ajax:aborted:file').off('ajax:error')
153
+
154
+ $(target).trigger('submit.rails')
155
+ .on 'ajax:aborted:file', (inputs) ->
156
+ false
157
+ .on 'ajax:error', (event, response, status)->
158
+ $(target).attr('action', action)
159
+ if response.status == 422
160
+ loadErrors(target)
161
+ .on 'ajax:success', (event, object, status, response) ->
162
+ $(target).attr('action', action)
163
+ if response.status == 201 # created
164
+ $(target).next().find('input:first').val(object.id)
165
+ # replace new form with edit form
166
+ # to update form method to PATCH and form action
167
+ url = "#{action}/#{object.id}/edit"
168
+ extractAndInsertForm(url, $(target).parent('fieldset'))
169
+ $(target).remove()
170
+
171
+ callback()
172
+
@@ -0,0 +1,25 @@
1
+ form .polymorphic_has_many_container fieldset
2
+ position: relative
3
+
4
+ form .polymorphic_has_many_container fieldset:not(:last-of-type)
5
+ border-bottom: 1px solid lightgray
6
+ margin-bottom: 20px
7
+
8
+ .input.refile img.image-preview
9
+ margin: 0.5em 0 0 20%
10
+ display: block
11
+
12
+ form .polymorphic_has_many_container .handle
13
+ position: absolute
14
+ top: calc(50% - 60px)
15
+ right: 2px
16
+ padding: 0
17
+ cursor: move
18
+
19
+ &.ui-sortable-handle
20
+ top: calc(50% - 42px)
21
+
22
+ span.icon
23
+ width: 3em
24
+ height: 3em
25
+
@@ -0,0 +1,5 @@
1
+ module ActiveadminPolymorphic
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ActiveadminPolymorphic
4
+ end
5
+ end
@@ -0,0 +1,124 @@
1
+ module ActiveadminPolymorphic
2
+ class FormBuilder < ::ActiveAdmin::FormBuilder
3
+ def polymorphic_has_many(assoc, poly_name, options = {}, &block)
4
+ custom_settings = :new_record, :allow_destroy, :heading, :sortable, :sortable_start, :types, :path_prefix
5
+ builder_options = {new_record: true, path_prefix: :admin}.merge! options.slice *custom_settings
6
+
7
+ options = {for: assoc }.merge! options.except *custom_settings
8
+ options[:class] = [options[:class], "polymorphic_has_many_fields"].compact.join(' ')
9
+ sortable_column = builder_options[:sortable]
10
+ sortable_start = builder_options.fetch(:sortable_start, 0)
11
+
12
+ html = "".html_safe
13
+ html << template.capture do
14
+ contents = "".html_safe
15
+
16
+ block = polymorphic_form(poly_name, builder_options)
17
+
18
+ template.assign('polymorphic_has_many_block' => true)
19
+ contents = without_wrapper { inputs(options, &block) }
20
+
21
+ if builder_options[:new_record]
22
+ contents << js_for_polymorphic_has_many(
23
+ assoc, poly_name, template, builder_options, options[:class]
24
+ )
25
+ else
26
+ contents
27
+ end
28
+ end
29
+
30
+ tag = @already_in_an_inputs_block ? :li : :div
31
+ html = template.content_tag(tag, html, class: "polymorphic_has_many_container #{assoc}", 'data-sortable' => sortable_column, 'data-sortable-start' => sortable_start)
32
+ template.concat(html) if template.output_buffer
33
+ html
34
+ end
35
+
36
+ protected
37
+
38
+ def polymorphic_has_many_actions(has_many_form, builder_options, contents)
39
+ if has_many_form.object.new_record?
40
+ contents << template.content_tag(:li) do
41
+ template.link_to I18n.t('active_admin.has_many_remove'),
42
+ "#", class: 'button polymorphic_has_many_remove'
43
+ end
44
+ elsif builder_options[:allow_destroy]
45
+ contents << has_many_form.input(:_destroy, as: :boolean,
46
+ wrapper_html: {class: 'polymorphic_has_many_delete'},
47
+ label: I18n.t('active_admin.has_many_delete'))
48
+ end
49
+
50
+ if builder_options[:sortable]
51
+ contents << has_many_form.input(builder_options[:sortable], as: :hidden)
52
+
53
+ contents << template.content_tag(:li, class: 'handle') do
54
+ ::ActiveAdmin::Iconic.icon :move_vertical
55
+ end
56
+ end
57
+
58
+ contents
59
+ end
60
+
61
+ def js_for_polymorphic_has_many(assoc, poly_name, template, builder_options, class_string)
62
+ new_record = builder_options[:new_record]
63
+ assoc_reflection = object.class.reflect_on_association assoc
64
+ assoc_name = assoc_reflection.klass.model_name
65
+ placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.gsub(/\//, '_')}_RECORD"
66
+
67
+ text = new_record.is_a?(String) ? new_record : I18n.t('active_admin.has_many_new', model: assoc_name.human)
68
+ form_block = polymorphic_form(poly_name, builder_options, true)
69
+
70
+ opts = {
71
+ for: [assoc, assoc_reflection.klass.new],
72
+ class: class_string,
73
+ for_options: { child_index: placeholder }
74
+ }
75
+
76
+ html = "".html_safe
77
+ html << template.capture do
78
+ inputs_for_nested_attributes opts, &form_block
79
+ end
80
+
81
+ template.link_to text, '#', class: "button polymorphic_has_many_add", data: {
82
+ html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
83
+ }
84
+ end
85
+
86
+ def polymorphic_options(builder_options)
87
+ # add internationalization
88
+ builder_options[:types].each_with_object([]) do |model, options|
89
+ options << [
90
+ model.model_name.human, model,
91
+ {"data-path" => form_new_path(model, builder_options) }
92
+ ]
93
+ end
94
+ end
95
+
96
+ def polymorphic_form(poly_name, builder_options, for_js = false)
97
+ proc do |f|
98
+ html = "".html_safe
99
+ html << f.input("#{poly_name}_id", as: :hidden)
100
+
101
+ if f.object.send(poly_name).nil?
102
+ html << f.input("#{poly_name}_type", input_html: { class: 'polymorphic_type_select' }, as: :select, collection: polymorphic_options(builder_options))
103
+ else
104
+ html << f.input(
105
+ "#{poly_name}_type", as: :hidden,
106
+ input_html: {"data-path" => form_edit_path(f.object.send(poly_name), builder_options) }
107
+ )
108
+ end
109
+
110
+ html << polymorphic_has_many_actions(f, builder_options, "".html_safe)
111
+
112
+ html
113
+ end
114
+ end
115
+
116
+ def form_new_path(object, builder_options)
117
+ "/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/new"
118
+ end
119
+
120
+ def form_edit_path(object, builder_options)
121
+ "/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/#{object.id}/edit"
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveadminPolymorphic
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "activeadmin_polymorphic/engine"
2
+ require "activeadmin_polymorphic/form_builder"
3
+
4
+ module ActiveadminPolymorphic
5
+ end