para 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 (203) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +65 -0
  4. data/Rakefile +21 -0
  5. data/app/assets/images/para/admin/bg.png +0 -0
  6. data/app/assets/javascripts/admin/app.coffee +1 -0
  7. data/app/assets/javascripts/para/admin/table.coffee +46 -0
  8. data/app/assets/javascripts/para/admin/theme_actions.coffee +26 -0
  9. data/app/assets/javascripts/para/admin/tree.coffee +53 -0
  10. data/app/assets/javascripts/para/admin.coffee +18 -0
  11. data/app/assets/javascripts/para/application.js +13 -0
  12. data/app/assets/javascripts/para/inputs/nested_many.coffee +57 -0
  13. data/app/assets/javascripts/para/lib/ajax.coffee +11 -0
  14. data/app/assets/stylesheets/admin/app.sass +0 -0
  15. data/app/assets/stylesheets/admin/theme.sass +0 -0
  16. data/app/assets/stylesheets/para/admin/theme/_base.sass +170 -0
  17. data/app/assets/stylesheets/para/admin/theme/_breadcrumb.sass +29 -0
  18. data/app/assets/stylesheets/para/admin/theme/_buttons.sass +24 -0
  19. data/app/assets/stylesheets/para/admin/theme/_checkable.sass +107 -0
  20. data/app/assets/stylesheets/para/admin/theme/_commonds.sass +213 -0
  21. data/app/assets/stylesheets/para/admin/theme/_dropdown.sass +53 -0
  22. data/app/assets/stylesheets/para/admin/theme/_form.sass +141 -0
  23. data/app/assets/stylesheets/para/admin/theme/_list.sass +138 -0
  24. data/app/assets/stylesheets/para/admin/theme/_navigation.sass +287 -0
  25. data/app/assets/stylesheets/para/admin/theme/_navtabs.sass +127 -0
  26. data/app/assets/stylesheets/para/admin/theme/_orderable.sass +44 -0
  27. data/app/assets/stylesheets/para/admin/theme/_panel.sass +288 -0
  28. data/app/assets/stylesheets/para/admin/theme/_responsive.sass +123 -0
  29. data/app/assets/stylesheets/para/admin/theme/_sorting.sass +30 -0
  30. data/app/assets/stylesheets/para/admin/theme/_table.sass +24 -0
  31. data/app/assets/stylesheets/para/admin/theme/_tree.sass +74 -0
  32. data/app/assets/stylesheets/para/admin/theme/_variables.sass +31 -0
  33. data/app/assets/stylesheets/para/admin/theme.sass +22 -0
  34. data/app/assets/stylesheets/para/admin.sass +10 -0
  35. data/app/assets/stylesheets/para/application.css +15 -0
  36. data/app/assets/stylesheets/para/lib/_variables.scss +646 -0
  37. data/app/assets/stylesheets/para/overrides/datetimepicker.sass +17 -0
  38. data/app/assets/stylesheets/para/overrides/fuelux.sass +43 -0
  39. data/app/assets/stylesheets/para/overrides/jasny.bootstrap.sass +41 -0
  40. data/app/assets/stylesheets/para/overrides/redactor.sass +88 -0
  41. data/app/assets/stylesheets/para/overrides/responsive.sass +63 -0
  42. data/app/assets/stylesheets/para/overrides/selectize.sass +45 -0
  43. data/app/assets/stylesheets/para/overrides/slider.sass +13 -0
  44. data/app/assets/stylesheets/para/overrides/theme.sass +172 -0
  45. data/app/controllers/concerns/para/admin/resource_controller_concerns.rb +17 -0
  46. data/app/controllers/para/admin/base_controller.rb +46 -0
  47. data/app/controllers/para/admin/component_controller.rb +9 -0
  48. data/app/controllers/para/admin/crud_component_controller.rb +17 -0
  49. data/app/controllers/para/admin/crud_resources_controller.rb +71 -0
  50. data/app/controllers/para/admin/main_controller.rb +8 -0
  51. data/app/controllers/para/admin/resources_controller.rb +163 -0
  52. data/app/controllers/para/admin/settings_component_controller.rb +11 -0
  53. data/app/controllers/para/admin/settings_form_controller.rb +21 -0
  54. data/app/controllers/para/admin/singleton_resource_component_controller.rb +14 -0
  55. data/app/controllers/para/admin/singleton_resources_controller.rb +30 -0
  56. data/app/controllers/para/application_controller.rb +7 -0
  57. data/app/decorators/para/component/base_decorator.rb +48 -0
  58. data/app/decorators/para/component/crud_decorator.rb +32 -0
  59. data/app/decorators/para/component/settings_decorator.rb +11 -0
  60. data/app/decorators/para/component/singleton_resource_decorator.rb +11 -0
  61. data/app/decorators/para/component_section_decorator.rb +4 -0
  62. data/app/helpers/para/admin/base_helper.rb +57 -0
  63. data/app/helpers/para/admin/component_groups_helper.rb +4 -0
  64. data/app/helpers/para/admin/components_helper.rb +4 -0
  65. data/app/helpers/para/admin/resources_helper.rb +27 -0
  66. data/app/helpers/para/application_helper.rb +14 -0
  67. data/app/helpers/para/exports_helper.rb +11 -0
  68. data/app/helpers/para/flash_helper.rb +48 -0
  69. data/app/helpers/para/form_helper.rb +28 -0
  70. data/app/helpers/para/markup_helper.rb +15 -0
  71. data/app/helpers/para/model_helper.rb +38 -0
  72. data/app/helpers/para/navigation_helper.rb +8 -0
  73. data/app/helpers/para/ordering_helper.rb +20 -0
  74. data/app/helpers/para/search_helper.rb +22 -0
  75. data/app/helpers/para/tag_helper.rb +50 -0
  76. data/app/helpers/para/tree_helper.rb +41 -0
  77. data/app/models/para/component/base.rb +70 -0
  78. data/app/models/para/component/crud.rb +45 -0
  79. data/app/models/para/component/resource.rb +33 -0
  80. data/app/models/para/component/settings.rb +17 -0
  81. data/app/models/para/component/singleton_resource.rb +25 -0
  82. data/app/models/para/component_resource.rb +6 -0
  83. data/app/models/para/component_section.rb +18 -0
  84. data/app/views/layouts/para/admin.html.haml +32 -0
  85. data/app/views/layouts/para/application.html.erb +14 -0
  86. data/app/views/para/admin/component_sections/_form.html.haml +10 -0
  87. data/app/views/para/admin/component_sections/edit.html.haml +5 -0
  88. data/app/views/para/admin/component_sections/new.html.haml +5 -0
  89. data/app/views/para/admin/components/_form.html.haml +15 -0
  90. data/app/views/para/admin/components/new.html.haml +4 -0
  91. data/app/views/para/admin/crud_component/show.html.haml +4 -0
  92. data/app/views/para/admin/dashboard.html.haml +10 -0
  93. data/app/views/para/admin/main/index.html.haml +1 -0
  94. data/app/views/para/admin/resources/_exports_menu.html.haml +12 -0
  95. data/app/views/para/admin/resources/_fields.html.haml +5 -0
  96. data/app/views/para/admin/resources/_filters.html.haml +9 -0
  97. data/app/views/para/admin/resources/_form.html.haml +6 -0
  98. data/app/views/para/admin/resources/_list.html.haml +22 -0
  99. data/app/views/para/admin/resources/_table.html.haml +8 -0
  100. data/app/views/para/admin/resources/_tree.html.haml +15 -0
  101. data/app/views/para/admin/resources/_tree_item.html.haml +16 -0
  102. data/app/views/para/admin/resources/edit.html.haml +6 -0
  103. data/app/views/para/admin/resources/new.html.haml +6 -0
  104. data/app/views/para/admin/settings_component/show.html.haml +12 -0
  105. data/app/views/para/admin/shared/_breadcrumb.html.haml +3 -0
  106. data/app/views/para/admin/shared/_header.html.haml +27 -0
  107. data/app/views/para/admin/shared/_navigation.html.haml +21 -0
  108. data/app/views/para/admin/singleton_resource_component/show.html.haml +5 -0
  109. data/app/views/para/inputs/_nested_many.html.haml +10 -0
  110. data/app/views/para/inputs/_nested_many_container.html.haml +19 -0
  111. data/app/views/para/inputs/_nested_one.html.haml +3 -0
  112. data/config/locales/en.yml +86 -0
  113. data/config/locales/fr.yml +101 -0
  114. data/db/migrate/20140911091225_create_para_components.rb +19 -0
  115. data/db/migrate/20140911112150_add_slug_to_para_components.rb +6 -0
  116. data/db/migrate/20140929125733_create_para_component_sections.rb +8 -0
  117. data/db/migrate/20140929131111_add_identifier_to_para_component_sections.rb +5 -0
  118. data/db/migrate/20140930121822_add_position_to_para_component_section.rb +5 -0
  119. data/db/migrate/20150129170710_create_para_component_resources.rb +13 -0
  120. data/db/migrate/20150203173219_add_identifier_to_para_components.rb +5 -0
  121. data/lib/generators/para/admin_user/admin_user_generator.rb +16 -0
  122. data/lib/generators/para/component/component_generator.rb +59 -0
  123. data/lib/generators/para/component/templates/component.rb +3 -0
  124. data/lib/generators/para/component/templates/component_controller.rb +7 -0
  125. data/lib/generators/para/component/templates/show.html.haml +2 -0
  126. data/lib/generators/para/exporter/exporter_generator.rb +42 -0
  127. data/lib/generators/para/exporter/templates/base_exporter.rb +15 -0
  128. data/lib/generators/para/exporter/templates/csv_exporter.rb +13 -0
  129. data/lib/generators/para/form/form_generator.rb +15 -0
  130. data/lib/generators/para/form/templates/_form.html.haml +7 -0
  131. data/lib/generators/para/install/install_generator.rb +112 -0
  132. data/lib/generators/para/install/templates/components.rb +13 -0
  133. data/lib/generators/para/install/templates/initializer.rb +23 -0
  134. data/lib/generators/para/nested_fields/nested_fields_generator.rb +15 -0
  135. data/lib/generators/para/nested_fields/templates/_nested_fields.html.haml +3 -0
  136. data/lib/generators/para/orderable/orderable_generator.rb +53 -0
  137. data/lib/generators/para/orderable/templates/orderable_migration.rb +6 -0
  138. data/lib/generators/para/resource/resource_generator.rb +71 -0
  139. data/lib/generators/para/resource/templates/resource_controller.rb +5 -0
  140. data/lib/generators/para/table/table_generator.rb +34 -0
  141. data/lib/generators/para/table/templates/_table.html.haml +10 -0
  142. data/lib/para/attribute_field/base.rb +77 -0
  143. data/lib/para/attribute_field/belongs_to.rb +29 -0
  144. data/lib/para/attribute_field/boolean.rb +15 -0
  145. data/lib/para/attribute_field/datetime.rb +9 -0
  146. data/lib/para/attribute_field/file.rb +20 -0
  147. data/lib/para/attribute_field/has_many.rb +31 -0
  148. data/lib/para/attribute_field/image.rb +25 -0
  149. data/lib/para/attribute_field/nested_many.rb +26 -0
  150. data/lib/para/attribute_field/nested_one.rb +27 -0
  151. data/lib/para/attribute_field/password.rb +18 -0
  152. data/lib/para/attribute_field/redactor.rb +20 -0
  153. data/lib/para/attribute_field/relation.rb +52 -0
  154. data/lib/para/attribute_field/translation.rb +9 -0
  155. data/lib/para/attribute_field_mappings.rb +82 -0
  156. data/lib/para/cloneable.rb +20 -0
  157. data/lib/para/component/exportable.rb +25 -0
  158. data/lib/para/component.rb +27 -0
  159. data/lib/para/components_configuration.rb +156 -0
  160. data/lib/para/config.rb +21 -0
  161. data/lib/para/engine.rb +59 -0
  162. data/lib/para/errors.rb +15 -0
  163. data/lib/para/exporter/base.rb +27 -0
  164. data/lib/para/exporter/csv.rb +49 -0
  165. data/lib/para/exporter.rb +57 -0
  166. data/lib/para/ext/cancan.rb +26 -0
  167. data/lib/para/ext/paperclip.rb +67 -0
  168. data/lib/para/ext.rb +13 -0
  169. data/lib/para/form_builder/containers.rb +70 -0
  170. data/lib/para/form_builder/field_mappings.rb +9 -0
  171. data/lib/para/form_builder/nested_form.rb +80 -0
  172. data/lib/para/form_builder/ordering.rb +15 -0
  173. data/lib/para/form_builder/settings.rb +24 -0
  174. data/lib/para/form_builder.rb +22 -0
  175. data/lib/para/generators/field_helpers.rb +32 -0
  176. data/lib/para/generators/name_helpers.rb +9 -0
  177. data/lib/para/generators/named_base.rb +7 -0
  178. data/lib/para/generators.rb +11 -0
  179. data/lib/para/inputs/nested_many_input.rb +40 -0
  180. data/lib/para/inputs/nested_one_input.rb +24 -0
  181. data/lib/para/inputs.rb +11 -0
  182. data/lib/para/markup/alert.rb +37 -0
  183. data/lib/para/markup/component.rb +20 -0
  184. data/lib/para/markup/modal.rb +52 -0
  185. data/lib/para/markup/panel.rb +41 -0
  186. data/lib/para/markup/resources_table.rb +178 -0
  187. data/lib/para/markup.rb +10 -0
  188. data/lib/para/model_field_parsers/base.rb +20 -0
  189. data/lib/para/model_field_parsers/devise.rb +37 -0
  190. data/lib/para/model_field_parsers/globalize.rb +23 -0
  191. data/lib/para/model_field_parsers/orderable.rb +15 -0
  192. data/lib/para/model_field_parsers/paperclip.rb +51 -0
  193. data/lib/para/model_field_parsers/redactor.rb +19 -0
  194. data/lib/para/model_field_parsers/relations.rb +67 -0
  195. data/lib/para/model_field_parsers.rb +22 -0
  196. data/lib/para/orderable.rb +44 -0
  197. data/lib/para/routes.rb +49 -0
  198. data/lib/para/version.rb +3 -0
  199. data/lib/para.rb +65 -0
  200. data/lib/rails/relation_length_validator.rb +45 -0
  201. data/lib/rails/routing_mapper.rb +27 -0
  202. data/lib/tasks/para_tasks.rake +22 -0
  203. metadata +595 -0
@@ -0,0 +1,53 @@
1
+ module Para
2
+ class OrderableGenerator < Rails::Generators::NamedBase
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ class_option :migrate, type: :boolean, default: false, :aliases => "-m"
8
+
9
+ desc 'Para orderable model updater'
10
+
11
+ def welcome
12
+ say "Making #{ class_name } model orderable ..."
13
+ end
14
+
15
+ def add_field_to_model
16
+ migration_template(
17
+ 'orderable_migration.rb',
18
+ "db/migrate/add_orderable_position_to_#{ plural_file_name }.rb"
19
+ )
20
+ end
21
+
22
+ def add_orderable_to_model
23
+ class_definition = "class #{ class_name } < ActiveRecord::Base\n"
24
+
25
+ inject_into_file "app/models/#{ file_name }.rb", after: class_definition do
26
+ " acts_as_orderable\n"
27
+ end
28
+ end
29
+
30
+ def migrate
31
+ rake 'db:migrate' if options[:migrate]
32
+ end
33
+
34
+ def fianl_message
35
+ message = "The #{ class_name } model is now orderable.\n"
36
+ message << "* Please migrate to update your model's table\n" unless options[:migrate]
37
+ message << <<-MESSAGE
38
+ * Please add to your model resource's routes the ordering path.
39
+ e.g.: resources :#{ plural_file_name } do
40
+ collection do
41
+ patch 'order'
42
+ end
43
+ end
44
+ MESSAGE
45
+
46
+ say(message)
47
+ end
48
+
49
+ def self.next_migration_number(dir)
50
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ class AddOrderablePositionTo<%= class_name.pluralize %> < ActiveRecord::Migration
2
+ def change
3
+ add_column :<%= table_name %>, :position, :integer, default: 0
4
+ add_index :<%= table_name %>, :position
5
+ end
6
+ end
@@ -0,0 +1,71 @@
1
+ module Para
2
+ class ResourceGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ argument :component_name, type: :string
6
+ argument :attributes, type: :array
7
+
8
+ class_option :migrate, type: :boolean, default: false, :aliases => "-m"
9
+ class_option :orderable, type: :boolean, default: false, :aliases => "-o"
10
+
11
+ desc 'Para resource generator'
12
+
13
+ def welcome
14
+ say 'Creating resource...'
15
+ end
16
+
17
+ def copy_resource_controller
18
+ template 'resource_controller.rb', "app/controllers/admin/#{ plural_file_name }_controller.rb"
19
+ end
20
+
21
+ def add_resource_to_component_controller
22
+ gsub_file "app/controllers/admin/#{ component_name.singularize }_component_controller.rb", /# You can access @component here/ do
23
+ <<-RUBY
24
+ @q = @component.#{ plural_file_name }.search(params[:q])
25
+ @resources = @q.result.page(params[:page])
26
+ RUBY
27
+ end
28
+ end
29
+
30
+ def insert_route
31
+ inject_into_file 'config/routes.rb', after: "component :#{ component_name.underscore } do" do
32
+ "\n resources :#{ plural_file_name }"
33
+ end
34
+ end
35
+
36
+ def insert_relation_to_show
37
+ append_to_file "app/views/admin/#{ component_name.underscore }_component/show.html.haml" do
38
+ "\n\n= listing_for(@resources)"
39
+ end
40
+ end
41
+
42
+ def generate_model
43
+ generate 'model',
44
+ file_name,
45
+ attributes.map { |attr|
46
+ "#{ attr.name }:#{ attr.type }"
47
+ }.insert(-1, 'component:references').join(' ')
48
+ end
49
+
50
+ def orderable
51
+ generate 'para:orderable', file_name if options[:orderable]
52
+ end
53
+
54
+ def migrate
55
+ rake 'db:migrate' if options[:migrate]
56
+ end
57
+
58
+ def insert_belongs_to_to_resource
59
+ inject_into_file "app/models/#{ file_name }.rb", after: "belongs_to :component" do
60
+ ", class_name: 'Para::Component::Base'"
61
+ end
62
+ end
63
+
64
+ def insert_has_many_to_component
65
+ inject_into_file "app/components/#{ component_name.underscore }_component.rb", after: "register :#{ component_name.underscore }, self" do
66
+ "\n\n has_many :#{ plural_file_name }, class_name: '::#{ class_name }', inverse_of: :component,
67
+ foreign_key: :component_id, dependent: :destroy"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ module Admin
2
+ class <%= plural_file_name.camelize %>Controller < Para::Admin::ResourcesController
3
+ resource :<%= file_name %>
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ module Para
2
+ class TableGenerator < Para::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ desc 'Para resources table generator'
6
+
7
+ def generate_table
8
+ template(
9
+ "_table.html.haml",
10
+ "app/views/admin/#{ plural_namespaced_path }/_table.haml"
11
+ )
12
+ end
13
+
14
+ private
15
+
16
+ def attributes
17
+ @attributes ||= begin
18
+ model =
19
+ begin
20
+ Para.const_get(class_name)
21
+ rescue
22
+ class_name.classify.constantize
23
+ end
24
+ AttributeFieldMappings.new(model).fields
25
+ end
26
+ end
27
+
28
+ def attributes_list
29
+ @attributes_list ||= attributes.map do |field|
30
+ field.name.to_sym.inspect
31
+ end.join(', ')
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ = resources_table(model: model, component: component) do |table|
2
+ = table.header do
3
+ <%- attributes.each do |field| -%>
4
+ = table.header_for(:<%= field.name %>)
5
+ <%- end -%>
6
+
7
+ = table.rows(resources) do |resource|
8
+ <%- attributes.each do |field| -%>
9
+ = table.data_for(resource, :<%= field.name %>)
10
+ <%- end -%>
@@ -0,0 +1,77 @@
1
+ module Para
2
+ module AttributeField
3
+ class Base
4
+ class_attribute :_field_options
5
+
6
+ attr_reader :model, :name, :type, :field_type, :field_method
7
+
8
+ def self.field_option(key, method_name, options = {})
9
+ self._field_options ||= []
10
+
11
+ self._field_options += [{
12
+ key: key,
13
+ method_name: method_name,
14
+ options: options
15
+ }]
16
+ end
17
+
18
+ field_option :as, :field_type_name
19
+
20
+ def initialize(model, options = {})
21
+ @model = model
22
+ @name = options[:name]
23
+ @type = options[:type]
24
+ @field_type = options[:field_type]
25
+
26
+ determine_name_and_field_method!
27
+ end
28
+
29
+ def determine_name_and_field_method!
30
+ name = @name
31
+
32
+ reference = model.reflect_on_all_associations.find do |association|
33
+ association.foreign_key == name
34
+ end
35
+
36
+ if reference
37
+ @name = reference.name
38
+ @field_method = :association
39
+ else
40
+ @name = name
41
+ @field_method = :input
42
+ end
43
+ end
44
+
45
+ def value_for(instance)
46
+ instance.send(name)
47
+ end
48
+
49
+ # Allows parsing input params before they're passed to the model, so
50
+ # it can be easy to edit them according to some field type specific
51
+ # behavior
52
+ #
53
+ def parse_input(params); end
54
+
55
+ def field_options
56
+ self.class._field_options.each_with_object({}) do |params, hash|
57
+ value = send(params[:method_name])
58
+ hash[params[:key]] = value if value != nil || params[:options][:allow_nil]
59
+ end
60
+ end
61
+
62
+ def field_name
63
+ name
64
+ end
65
+
66
+ def attribute_column_path
67
+ [name]
68
+ end
69
+
70
+ private
71
+
72
+ def field_type_name
73
+ field_type.presence && field_type.to_sym
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ module Para
2
+ module AttributeField
3
+ class BelongsToField < RelationField
4
+ field_option :collection, :relation_options
5
+
6
+ def field_name
7
+ reflection.name
8
+ end
9
+
10
+ def value_for(instance)
11
+ if (resource = instance.send(name))
12
+ resource_name(resource)
13
+ end
14
+ end
15
+
16
+ def relation_options
17
+ reflection.klass.all
18
+ end
19
+
20
+ def parse_input(params)
21
+ if (id = params[reflection.foreign_key].presence) && !reflection.klass.exists?(id: id)
22
+ on_the_fly_creation(id) do |resource|
23
+ params[reflection.foreign_key] = resource.id
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module Para
2
+ module AttributeField
3
+ class BooleanField < AttributeField::Base
4
+ field_option :wrapper, :wrapper_name
5
+
6
+ def value_for(instance)
7
+ I18n.t("para.types.boolean.#{ (!!instance.send(name)).to_s }")
8
+ end
9
+
10
+ def wrapper_name
11
+ :horizontal_radio_and_checkboxes
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Para
2
+ module AttributeField
3
+ class DatetimeField < AttributeField::Base
4
+ def value_for(instance)
5
+ (value = instance.send(name)) && I18n.l(value)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ module Para
2
+ module AttributeField
3
+ class FileField < AttributeField::Base
4
+ include ActionView::Helpers::UrlHelper
5
+
6
+ field_option :wrapper, :wrapper_name
7
+
8
+ def value_for(instance)
9
+ if instance.send(:"#{ name }?")
10
+ url = instance.send(name).url
11
+ link_to(url, url)
12
+ end
13
+ end
14
+
15
+ def wrapper_name
16
+ :horizontal_file_input
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Para
2
+ module AttributeField
3
+ class HasManyField < RelationField
4
+ def field_name
5
+ reflection.name
6
+ end
7
+
8
+ def value_for(instance)
9
+ instance.send(name).map do |resource|
10
+ resource_name(resource)
11
+ end.join(', ')
12
+ end
13
+
14
+ def parse_input(params)
15
+ if (ids = params[plural_foreign_key].presence) && String === ids
16
+ # Format selectize value for Rails
17
+ ids = params[plural_foreign_key] = ids.split(',')
18
+
19
+ on_the_fly_creation(ids) do |resource, value|
20
+ params[plural_foreign_key].delete(value)
21
+ params[plural_foreign_key] << resource.id
22
+ end
23
+ end
24
+ end
25
+
26
+ def plural_foreign_key
27
+ foreign_key.to_s.pluralize
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ module Para
2
+ module AttributeField
3
+ class ImageField < AttributeField::Base
4
+ include ActionView::Helpers::AssetTagHelper
5
+
6
+ def value_for(instance)
7
+ style = attachment_thumb_style_for(instance)
8
+
9
+ if instance.send(:"#{ name }?")
10
+ image_tag(instance.send(name).url(style))
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def attachment_thumb_style_for(instance)
17
+ styles = instance.send(name).styles.map(&:first)
18
+ # Check if there's a :thumb or :thumbnail style in attachment definition
19
+ thumb = styles.find { |s| %w(thumb thumbnail).include?(s.to_s) }
20
+ # Return the potentially smallest size !
21
+ thumb || styles.first || :original
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Para
2
+ module AttributeField
3
+ class NestedManyField < AttributeField::HasManyField
4
+ def parse_input(params)
5
+ if (nested_attributes = params[nested_attributes_key])
6
+ nested_attributes.each do |index, attributes|
7
+ nested_model_mappings.fields.each do |field|
8
+ field.parse_input(attributes)
9
+ end
10
+ params[nested_attributes_key][index] = attributes
11
+ end
12
+ else
13
+ super(params)
14
+ end
15
+ end
16
+
17
+ def nested_model_mappings
18
+ @nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
19
+ end
20
+
21
+ def nested_attributes_key
22
+ @nested_attributes_key ||= :"#{ name }_attributes"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ module Para
2
+ module AttributeField
3
+ class NestedOneField < AttributeField::BelongsToField
4
+ def parse_input(params)
5
+ if (nested_attributes = params[nested_attributes_key])
6
+ nested_attributes.each do |index, attributes|
7
+ nested_model_mappings.fields.each do |field|
8
+ field.parse_input(attributes)
9
+ end
10
+
11
+ params[nested_attributes_key][index] = attributes
12
+ end
13
+ else
14
+ super(params)
15
+ end
16
+ end
17
+
18
+ def nested_model_mappings
19
+ @nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
20
+ end
21
+
22
+ def nested_attributes_key
23
+ @nested_attributes_key ||= :"#{ name }_attributes"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module Para
2
+ module AttributeField
3
+ class PasswordField < AttributeField::Base
4
+ def initialize(model, options = {})
5
+ options.reverse_merge!(type: 'password', field_type: 'password')
6
+ super(model, options)
7
+ end
8
+
9
+ def value_for(instance)
10
+ nil
11
+ end
12
+
13
+ def parse_input(params)
14
+ params[name] = params[name].presence
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Para
2
+ module AttributeField
3
+ class Redactor < AttributeField::Base
4
+ include ActionView::Helpers::SanitizeHelper
5
+ include ActionView::Helpers::TextHelper
6
+
7
+ def initialize(model, options = {})
8
+ options.reverse_merge!(type: 'text', field_type: 'redactor')
9
+ super(model, options)
10
+ end
11
+
12
+ def value_for(instance)
13
+ (value = instance.send(name)) && truncate(
14
+ sanitize(value, tags: []),
15
+ length: 50
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ module Para
2
+ module AttributeField
3
+ class RelationField < AttributeField::Base
4
+ private
5
+
6
+ def reflection
7
+ @reflection ||= model.reflect_on_association(name)
8
+ end
9
+
10
+ def foreign_key
11
+ @foreign_key ||= reflection && case reflection.macro
12
+ when :belongs_to then reflection.foreign_key
13
+ when :has_one then :"#{ reflection.name }_id"
14
+ when :has_many then :"#{ reflection.name.to_s.singularize }_ids"
15
+ end
16
+ end
17
+
18
+ def resource_name(resource)
19
+ Para.config.resource_name_methods.each do |method|
20
+ return resource.send(method) if resource.respond_to?(method)
21
+ end
22
+
23
+ model_name = resource.class.model_name.human
24
+ "#{ model_name } - #{ resource.id }"
25
+ end
26
+
27
+ # Takes an array of ids and a block. Check for each id if model exists
28
+ # and create one if not. E.g: [12, "foo"] will try to create a model with
29
+ # 'foo'
30
+ def on_the_fly_creation ids, &block
31
+ Array.wrap(ids).each do |id|
32
+ if !reflection.klass.exists?(id: id)
33
+ resource = reflection.klass.new
34
+
35
+ Para.config.resource_name_methods.each do |method_name|
36
+ setter_name = :"#{ method_name }="
37
+
38
+ if resource.respond_to?(setter_name)
39
+ resource.send(setter_name, id)
40
+
41
+ if resource.save
42
+ block.call(resource, id)
43
+ break
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ module Para
2
+ module AttributeField
3
+ class Translation < AttributeField::Base
4
+ def attribute_column_path
5
+ [:translations, name]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,82 @@
1
+ require 'para/attribute_field/base'
2
+ require 'para/attribute_field/boolean'
3
+ require 'para/attribute_field/datetime'
4
+ require 'para/attribute_field/password'
5
+ require 'para/attribute_field/file'
6
+ require 'para/attribute_field/image'
7
+ require 'para/attribute_field/relation'
8
+ require 'para/attribute_field/belongs_to'
9
+ require 'para/attribute_field/has_many'
10
+ require 'para/attribute_field/nested_one'
11
+ require 'para/attribute_field/nested_many'
12
+ require 'para/attribute_field/redactor'
13
+ require 'para/attribute_field/translation'
14
+
15
+ module Para
16
+ class AttributeFieldMappings
17
+ UNEDITABLE_ATTRIBUTES = %w(id component_id created_at updated_at type)
18
+
19
+ attr_reader :model, :fields_hash, :whitelist_attributes
20
+
21
+ def initialize(model, whitelist_attributes: nil)
22
+ @model = model
23
+ @whitelist_attributes = whitelist_attributes
24
+
25
+ process_fields!
26
+ end
27
+
28
+ def fields
29
+ fields_hash.values
30
+ end
31
+
32
+ def field_for(field_name, type = nil)
33
+ fields_hash[field_name] ||= if model.new.respond_to?(field_name)
34
+ build_field_for(field_name, type)
35
+ else
36
+ raise NoMethodError.new(
37
+ "No attribute or method correspond to ##{ field_name } " +
38
+ "in the model #{ model.name }. No field could be created."
39
+ )
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def whitelisted?(attribute_name)
46
+ !whitelist_attributes || whitelist_attributes.include?(attribute_name)
47
+ end
48
+
49
+ def process_fields!
50
+ @fields_hash = model.columns.each_with_object({}) do |column, fields|
51
+ next unless whitelisted?(column.name)
52
+
53
+ # Reject uneditable attributes
54
+ unless UNEDITABLE_ATTRIBUTES.include?(column.name)
55
+ field_class = field_class_for(column.type)
56
+
57
+ fields[column.name] = field_class.new(
58
+ model, name: column.name, type: column.type
59
+ )
60
+ end
61
+ end.with_indifferent_access
62
+
63
+ Para::ModelFieldParsers.parse!(model, fields_hash)
64
+ end
65
+
66
+ def build_field_for(attribute_name, type)
67
+ field_class = field_class_for(type)
68
+
69
+ field_class.new(
70
+ model, name: attribute_name, type: type
71
+ )
72
+ end
73
+
74
+ def field_class_for(type)
75
+ case type
76
+ when :boolean then AttributeField::BooleanField
77
+ when :date, :datetime then AttributeField::DatetimeField
78
+ else AttributeField::Base
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,20 @@
1
+ module Para
2
+ module Cloneable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :cloneable_associations
7
+ end
8
+
9
+ module ClassMethods
10
+ def acts_as_cloneable(*args)
11
+ @cloneable = true
12
+ self.cloneable_associations = args
13
+ end
14
+
15
+ def cloneable?
16
+ @cloneable ||= false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module Para
2
+ module Component
3
+ module Exportable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ configurable_on :export
8
+ end
9
+
10
+ def exportable?
11
+ @exportable ||= exports.length > 0
12
+ end
13
+
14
+ # TODO : Move :configuration column store to JSON instead of HStore
15
+ # which handles more data types and will help us avoid eval here
16
+ def exports
17
+ @exports ||= if export.present?
18
+ eval(export).map(&:with_indifferent_access)
19
+ else
20
+ []
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end