activeadmin-magicfields 0.4.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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/Gemfile +42 -0
  4. data/Guardfile +8 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +160 -0
  7. data/Rakefile +33 -0
  8. data/activeadmin-magicfields.gemspec +23 -0
  9. data/app/assets/javascripts/activeadmin-magicfields.js.coffee +101 -0
  10. data/app/assets/stylesheets/activeadmin-magicfields.css.scss +53 -0
  11. data/lib/activeadmin-magicfields.rb +18 -0
  12. data/lib/activeadmin-magicfields/engine.rb +12 -0
  13. data/lib/activeadmin-magicfields/form_builder.rb +175 -0
  14. data/lib/activeadmin-magicfields/paperclip.rb +2 -0
  15. data/lib/activeadmin-magicfields/version.rb +3 -0
  16. data/lib/app/admin/part.rb +29 -0
  17. data/lib/app/admin/part_object.rb +26 -0
  18. data/lib/app/models/field/checkbox.rb +12 -0
  19. data/lib/app/models/field/image.rb +35 -0
  20. data/lib/app/models/field/repeater.rb +19 -0
  21. data/lib/app/models/field/text.rb +14 -0
  22. data/lib/app/models/field/textarea.rb +14 -0
  23. data/lib/app/models/field_template.rb +5 -0
  24. data/lib/app/models/part.rb +6 -0
  25. data/lib/app/models/part_object.rb +11 -0
  26. data/lib/app/models/part_object_field.rb +13 -0
  27. data/lib/app/models/repeater_part_object.rb +6 -0
  28. data/lib/app/views/admin/field_inputs/_checkbox.html.erb +1 -0
  29. data/lib/app/views/admin/field_inputs/_image.html.erb +4 -0
  30. data/lib/app/views/admin/field_inputs/_product.html.erb +1 -0
  31. data/lib/app/views/admin/field_inputs/_repeater.html.erb +3 -0
  32. data/lib/app/views/admin/field_inputs/_site_page.html.erb +1 -0
  33. data/lib/app/views/admin/field_inputs/_text.html.erb +1 -0
  34. data/lib/app/views/admin/field_inputs/_textarea.html.erb +8 -0
  35. data/lib/app/views/admin/part_objects/_part_object.html.erb +53 -0
  36. data/lib/app/views/admin/parts/_part.html.erb +11 -0
  37. data/lib/app/views/admin/parts/_part_old.arb +32 -0
  38. data/lib/generators/activeadmin-magicfields/install/install_generator.rb +33 -0
  39. data/lib/generators/activeadmin-magicfields/install/templates/create_field_checkbox.rb +10 -0
  40. data/lib/generators/activeadmin-magicfields/install/templates/create_field_image.rb +10 -0
  41. data/lib/generators/activeadmin-magicfields/install/templates/create_field_repeater.rb +9 -0
  42. data/lib/generators/activeadmin-magicfields/install/templates/create_field_templates.rb +14 -0
  43. data/lib/generators/activeadmin-magicfields/install/templates/create_field_text.rb +10 -0
  44. data/lib/generators/activeadmin-magicfields/install/templates/create_field_textarea.rb +12 -0
  45. data/lib/generators/activeadmin-magicfields/install/templates/create_part_object_fields.rb +10 -0
  46. data/lib/generators/activeadmin-magicfields/install/templates/create_part_objects.rb +13 -0
  47. data/lib/generators/activeadmin-magicfields/install/templates/create_parts.rb +9 -0
  48. data/lib/generators/activeadmin-magicfields/install/templates/create_repeater_part_objects.rb +10 -0
  49. data/spec/rails_helper.rb +154 -0
  50. data/spec/spec_helper.rb +17 -0
  51. data/spec/support/deferred_garbage_collection.rb +19 -0
  52. data/spec/support/detect_rails_version.rb +26 -0
  53. data/spec/support/integration_example_group.rb +31 -0
  54. data/spec/support/jslint.yml +80 -0
  55. data/spec/support/rails_template.rb +104 -0
  56. data/spec/support/rails_template_with_data.rb +59 -0
  57. data/spec/support/templates/cucumber.rb +24 -0
  58. data/spec/support/templates/cucumber_with_reloading.rb +5 -0
  59. data/spec/support/templates/en.yml +8 -0
  60. data/spec/support/templates/policies/active_admin/comment_policy.rb +9 -0
  61. data/spec/support/templates/policies/active_admin/page_policy.rb +18 -0
  62. data/spec/support/templates/policies/application_policy.rb +45 -0
  63. data/spec/support/templates/policies/category_policy.rb +7 -0
  64. data/spec/support/templates/policies/post_policy.rb +15 -0
  65. data/spec/support/templates/policies/store_policy.rb +11 -0
  66. data/spec/support/templates/policies/user_policy.rb +11 -0
  67. data/spec/support/templates/post_decorator.rb +11 -0
  68. data/spec/unit/form_builder_spec.rb +121 -0
  69. data/tasks/test.rake +83 -0
  70. metadata +175 -0
@@ -0,0 +1,53 @@
1
+ form .json_container {
2
+ position: relative;
3
+ }
4
+
5
+ form .json_container fieldset {
6
+ position: relative;
7
+ }
8
+
9
+ fieldset.jsonb {
10
+ padding: 20px;
11
+ border: solid 1px #ddd;
12
+ margin: 15px;
13
+ background: #f4f4f4;
14
+ .section-title {
15
+ display: inline-block;
16
+ }
17
+ .destroy-section {
18
+ display: inline-block;
19
+ margin-left: 15px;
20
+ }
21
+ li.hidden {
22
+ display: none;
23
+ }
24
+ }
25
+
26
+ .section_has_many_add {
27
+ margin: 20px 15px;
28
+ }
29
+
30
+ .input.refile img.image-preview {
31
+ margin: 0.5em 0 0 20%;
32
+ display: block;
33
+ }
34
+
35
+ form .jsonb fieldset.inputs {
36
+ box-shadow: none;
37
+ -webkit-box-shadow: none;
38
+ }
39
+
40
+ form .json_container .handle {
41
+ position: absolute;
42
+ right: 2px;
43
+ padding: 0;
44
+ cursor: move;
45
+ top: calc(50% - 34px/2);
46
+
47
+ span.icon {
48
+ width: 3em;
49
+ height: 3em;
50
+ display: inline-block;
51
+ }
52
+ }
53
+
@@ -0,0 +1,18 @@
1
+ require "activeadmin-magicfields/engine"
2
+ require "activeadmin-magicfields/form_builder"
3
+ require "activeadmin-magicfields/paperclip"
4
+
5
+
6
+ require "app/models/field_template"
7
+ require "app/models/part.rb"
8
+ require "app/models/part_object"
9
+ require "app/models/part_object_field"
10
+ require "app/models/repeater_part_object"
11
+ require "app/models/field/checkbox"
12
+ require "app/models/field/image"
13
+ require "app/models/field/repeater"
14
+ require "app/models/field/text"
15
+ require "app/models/field/textarea"
16
+
17
+ module ActiveadminMagicfields
18
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveadminMagicfields
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ActiveadminMagicfields
4
+
5
+ initializer "register resource" do
6
+ ActiveAdmin.application.load_paths += [ File.expand_path('../app/admin', File.dirname(__FILE__)) ]
7
+ end
8
+
9
+ paths["app/views"] << "lib/app/views"
10
+
11
+ end
12
+ end
@@ -0,0 +1,175 @@
1
+ module ActiveadminMagicfields
2
+ class FormBuilder < ::ActiveAdmin::FormBuilder
3
+
4
+ def sections_has_many(assoc, options = {}, &block)
5
+ custom_settings = :new_record, :allow_destroy, :heading, :sortable, :sortable_start, :types, :path_prefix, :part_objects, :form_prefix
6
+ builder_options = {new_record: true, path_prefix: :admin, part_objects: nil, form_prefix: ''}.merge! options.slice *custom_settings
7
+ options = {for: assoc }.merge! options.except *custom_settings
8
+ options[:class] = [options[:class], "jsonb"].compact.join(' ')
9
+ sortable_column = builder_options[:sortable]
10
+ sortable_start = builder_options.fetch(:sortable_start, 0)
11
+
12
+ collection = options[:collection]
13
+
14
+ html = "".html_safe
15
+ html << template.capture do
16
+ contents = "".html_safe
17
+ block = sections_form(assoc, builder_options, collection)
18
+ template.assign('sections_has_many_block' => true)
19
+ # this is the 'select new part' select box
20
+ # contents = without_wrapper { inputs(options, &block) }
21
+
22
+ builder_options[:part_objects].each do |part_object|
23
+ if part_object.part.blank?
24
+ part = Part.new
25
+ part_object.part = part
26
+ end
27
+ contents << @template.render("admin/part_objects/part_object", part_object: part_object, part:part_object.part, form_prefix: builder_options[:form_prefix] + @object_name)
28
+ end
29
+ # this is the 'Add new part button'
30
+ contents << js_for_section_has_many(
31
+ assoc, template, builder_options, options[:class], collection
32
+ )
33
+
34
+ end
35
+ tag = @already_in_an_inputs_block ? :li : :div
36
+ html = template.content_tag(tag, html, class: "json_container #{assoc}", 'data-sortable' => sortable_column)
37
+ template.concat(html) if template.output_buffer
38
+ html
39
+ end
40
+
41
+ protected
42
+
43
+ def json_actions(form, builder_options, contents)
44
+ if @object.new_record?
45
+ contents << template.content_tag(:li) do
46
+ template.link_to I18n.t('active_admin.has_many_remove'),
47
+ "#", class: 'button section_has_many_remove'
48
+ end
49
+ elsif builder_options[:allow_destroy]
50
+ contents << form.input(:_destroy, as: :boolean,
51
+ wrapper_html: {class: 'section_has_many_delete'},
52
+ label: I18n.t('active_admin.has_many_delete'))
53
+ end
54
+
55
+ contents
56
+ end
57
+
58
+
59
+ def js_for_section_has_many(assoc, template, builder_options, class_string, collection)
60
+ new_record = builder_options[:new_record]
61
+ assoc_reflection = object.class.reflect_on_association assoc
62
+ assoc_name = assoc_reflection.klass.model_name
63
+ placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.gsub(/\//, '_')}_RECORD"
64
+
65
+ text = new_record.is_a?(String) ? new_record : I18n.t('active_admin.has_many_new', model: assoc_name.human)
66
+ form_block = sections_form(assoc, builder_options, collection)
67
+
68
+ opts = {
69
+ for: [assoc, assoc_reflection.klass.new],
70
+ class: class_string,
71
+ for_options: { child_index: placeholder }
72
+ }
73
+ html = "".html_safe
74
+ html << template.capture do
75
+ inputs_for_nested_attributes opts, &form_block
76
+ end
77
+
78
+
79
+ template.link_to text, '#', class: "button section_has_many_add", data: {
80
+ html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
81
+ }
82
+ end
83
+
84
+ def sections_form(field_name, builder_options, collection)
85
+ proc do |form|
86
+ html = "".html_safe
87
+
88
+ # if @object.send(field_name).blank? # this is probably always true, since the :parts relationship is never actually created in the DB
89
+ html << form.input("name", input_html: { class: 'section_type_select', name: builder_options[:form_prefix] + @object_name }, as: :select, collection: section_options(collection, builder_options))
90
+ # else
91
+ # @object.send(field_name).to_a.last.last["fields"].each do |field|
92
+ # html << form.input("#{@object.send(field_name).first.first.to_i}[fields][#{field.first}][#{field.last.first.first}]", label: field.last.first.first.try(:humanize), input_html: {value: field.last.first.last})
93
+ # end
94
+ # end
95
+
96
+ html << json_actions(form, builder_options, "".html_safe)
97
+
98
+ html
99
+ end
100
+ end
101
+
102
+ def form_new_path(object, builder_options)
103
+ "/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/new"
104
+ end
105
+
106
+ def form_edit_path(object, builder_options)
107
+ "/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/#{object.id}/edit"
108
+ end
109
+
110
+
111
+ def section_options(collection, builder_options)
112
+ collection.each_with_object([]) do |model, options|
113
+ options << [
114
+ model.name, model,
115
+ {"data-path" => section_form_edit_path(model, builder_options) }
116
+ ]
117
+ end
118
+ end
119
+
120
+ def jsonbuilder(assoc, options = {}, &block)
121
+ custom_settings = :new_record, :allow_destroy, :heading, :position, :sortable_start, :types, :path_prefix
122
+ builder_options = {new_record: true, path_prefix: :admin}.merge! options.slice *custom_settings
123
+ options = {for: assoc }.merge! options.except *custom_settings
124
+ options[:class] = [options[:class], "jsonb"].compact.join(' ')
125
+ sortable_column = builder_options[:position]
126
+ sortable_start = builder_options.fetch(:sortable_start, 0)
127
+ types = options[:types]
128
+
129
+ html = "".html_safe
130
+ html << template.capture do
131
+ contents = "".html_safe
132
+ block = json_form(assoc, builder_options)
133
+ contents = without_wrapper { inputs(options, &block) }
134
+ end
135
+ tag = :li
136
+ html = template.content_tag(tag, html, class: "json_container #{assoc}", 'data-sortable' => sortable_column, 'data-sortable-start' => sortable_start)
137
+ template.concat(html) if template.output_buffer
138
+ html
139
+ end
140
+
141
+ def json_form(field_name, builder_options)
142
+ proc do |form|
143
+ html = "".html_safe
144
+
145
+ if @object.send(field_name).nil?
146
+ html << form.input("#{field_name}_type", input_html: { class: 'json_type_select' }, as: :select, collection: json_options(builder_options))
147
+ else
148
+ @object.send(field_name).to_a.last.last.each do |section_array|
149
+ html << form.input("#{@object.send(field_name).to_a.last.first}[#{section_array.first}]", as: :text, label: section_array.first.try(:humanize), input_html: {value: section_array.last})
150
+ end
151
+ end
152
+
153
+ html << json_actions(form, builder_options, "".html_safe)
154
+
155
+ html
156
+ end
157
+ end
158
+
159
+ def json_options(builder_options)
160
+ # add internationalization
161
+ builder_options[:types].each_with_object([]) do |model, options|
162
+ options << [
163
+ model.model_name.human, model,
164
+ {"data-path" => form_new_path(model, builder_options) }
165
+ ]
166
+ end
167
+ end
168
+
169
+ def section_form_edit_path(object, builder_options)
170
+ "/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/#{object.id}/serialized"
171
+ end
172
+
173
+
174
+ end
175
+ end
@@ -0,0 +1,2 @@
1
+ require "paperclip/railtie"
2
+ Paperclip::Railtie.insert
@@ -0,0 +1,3 @@
1
+ module ActiveadminMagicfields
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,29 @@
1
+ ActiveAdmin.register Part do
2
+
3
+ form do |f|
4
+ f.inputs "Part" do
5
+ f.input :name
6
+ end
7
+
8
+ f.inputs "Fields" do
9
+ f.has_many :field_templates, sortable: :position, allow_destroy: true do |a|
10
+ a.input :title
11
+ a.input :field_type, as: :select, collection: Field.constants.select {|c| Field.const_get(c).is_a? Class }.map { |f| f.to_s.underscore}
12
+ a.input :is_required
13
+ end
14
+ end
15
+
16
+ f.actions
17
+ end
18
+
19
+ member_action :serialized, :method => :get do
20
+ render resource
21
+ end
22
+
23
+ controller do
24
+ def permitted_params
25
+ params.permit!
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,26 @@
1
+ ActiveAdmin.register PartObject do
2
+
3
+ menu false
4
+
5
+ FIELDABLES = Field.constants.select {|c| Field.const_get(c).is_a? Class }.map {|f| Field.const_get(f) }
6
+
7
+ form builder: ActiveadminMagicfields::FormBuilder, :html => { :'data-type' => :json, :enctype => "multipart/form-data", :remote => true } do |f|
8
+ f.inputs do
9
+ f.input :base_model_id, as: :hidden
10
+ f.magicfields_has_many :part_object_fields, :fieldable, types: FIELDABLES, sortable: :position, allow_destroy: true
11
+ end
12
+ f.actions
13
+ end
14
+
15
+ # permit_params do
16
+ # permitted = PartObject.new.attributes.keys | [:id, part_object_fields_attributes: [:id, :fieldable_type, :fieldable_id, :position, :_destroy, fieldable_attributes: [:id] ] ]
17
+ # permitted
18
+ # end
19
+
20
+ controller do
21
+ def permitted_params
22
+ params.permit!
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,12 @@
1
+ module Field
2
+ class Checkbox < ActiveRecord::Base
3
+ self.table_name = "field_checkboxes"
4
+
5
+ has_one :part_object_field, as: :fieldable
6
+ belongs_to :field_template
7
+
8
+ delegate :title, to: :field_template, allow_nil: true
9
+ delegate :is_required, to: :field_template, allow_nil: true
10
+
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Field
2
+ class Image < ActiveRecord::Base
3
+ self.table_name = "field_images"
4
+
5
+ before_validation :destroy_image?
6
+
7
+ has_one :part_object_field, as: :fieldable
8
+ belongs_to :field_template
9
+
10
+ has_attached_file :image, :url => "/active_admin/:class/:attachment/:id/:style_:basename.:extension", :styles => { :medium => "500x500>", :thumb => "100x100>" }
11
+ # include DeletableAttachment
12
+
13
+ validates :image, presence: { if: :is_required }
14
+ # validates_attachment_presence :image
15
+ validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
16
+
17
+ delegate :title, to: :field_template, allow_nil: true
18
+ delegate :is_required, to: :field_template, allow_nil: true
19
+
20
+ def delete_image
21
+ @delete_image ||= "0"
22
+ end
23
+
24
+ def delete_image=(value)
25
+ @delete_image = value
26
+ image_file_name_will_change!
27
+ end
28
+
29
+ private
30
+
31
+ def destroy_image?
32
+ self.image.clear if @delete_image == "1"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ module Field
2
+ class Repeater < ActiveRecord::Base
3
+ self.table_name = "field_repeaters"
4
+
5
+ has_many :repeater_part_objects, class_name: "RepeaterPartObject", inverse_of: "field_repeater", foreign_key: "field_repeater_id", dependent: :destroy
6
+ has_many :part_objects, through: :repeater_part_objects, dependent: :destroy
7
+
8
+ has_many :parts
9
+
10
+ belongs_to :field_template
11
+
12
+ delegate :title, to: :field_template, allow_nil: true
13
+ delegate :is_required, to: :field_template, allow_nil: true
14
+
15
+ accepts_nested_attributes_for :repeater_part_objects, allow_destroy: true
16
+ accepts_nested_attributes_for :part_objects, allow_destroy: true
17
+
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Field
2
+ class Text < ActiveRecord::Base
3
+ self.table_name = "field_texts"
4
+
5
+ has_one :part_object_field, as: :fieldable
6
+ belongs_to :field_template
7
+
8
+ delegate :title, to: :field_template, allow_nil: true
9
+ delegate :is_required, to: :field_template, allow_nil: true
10
+
11
+ validates :text, presence: { if: :is_required }
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Field
2
+ class Textarea < ActiveRecord::Base
3
+ self.table_name = "field_textareas"
4
+
5
+ has_one :part_object_field, as: :fieldable
6
+ belongs_to :field_template
7
+
8
+ delegate :title, to: :field_template, allow_nil: true
9
+ delegate :is_required, to: :field_template, allow_nil: true
10
+
11
+ validates :textarea, presence: { if: :is_required }
12
+
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class FieldTemplate < ActiveRecord::Base
2
+ belongs_to :part
3
+
4
+ scope :by_position, -> { order('position ASC') }
5
+ end
@@ -0,0 +1,6 @@
1
+ class Part < ActiveRecord::Base
2
+ has_many :field_templates, dependent: :destroy
3
+ has_many :part_objects, dependent: :destroy
4
+ has_and_belongs_to_many :admin_users
5
+ accepts_nested_attributes_for :field_templates, allow_destroy: true
6
+ end