activeadmin_polymorphic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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