activeadmin-magicfields 0.4.0

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