para 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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,27 @@
1
+ module Para
2
+ module Component
3
+ def self.registered_components
4
+ @registered_components ||= {}
5
+ end
6
+
7
+ def self.registered_component?(model_name)
8
+ registered_components.any? do |name, component|
9
+ component.name == model_name
10
+ end
11
+ end
12
+
13
+ def self.config
14
+ @config ||= Para::ComponentsConfiguration.new
15
+ end
16
+ end
17
+ end
18
+
19
+ # Require concerns
20
+ require 'para/component/exportable'
21
+
22
+ # Require models
23
+ require 'para/component/base'
24
+ require 'para/component/resource'
25
+ require 'para/component/crud'
26
+ require 'para/component/singleton_resource'
27
+ require 'para/component/settings'
@@ -0,0 +1,156 @@
1
+ module Para
2
+ class ComponentsConfiguration
3
+ def draw(&block)
4
+ return unless components_installed?
5
+ eager_load_components!
6
+ instance_eval(&block)
7
+ build
8
+ end
9
+
10
+ def section(*args, &block)
11
+ sections << Section.new(*args, &block)
12
+ end
13
+
14
+ def sections
15
+ @sections ||= []
16
+ end
17
+
18
+ def method_missing(method, *args, &block)
19
+ component_for(method) || super
20
+ end
21
+
22
+ def section_for(identifier)
23
+ if (section = sections_cache[identifier])
24
+ section
25
+ elsif (section_id = sections_ids_hash[identifier])
26
+ sections_cache[identifier] = Para::ComponentSection.find(section_id)
27
+ end
28
+ end
29
+
30
+ def component_for(identifier)
31
+ if (component = components_cache[identifier])
32
+ component
33
+ elsif (component_id = components_ids_hash[identifier])
34
+ components_cache[identifier] = Para::Component::Base.find(component_id)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def build
41
+ sections.each_with_index do |section, index|
42
+ section.refresh(position: index)
43
+ sections_ids_hash[section.identifier] = section.model.id
44
+
45
+ section.components.each do |component|
46
+ components_ids_hash[component.identifier] = component.model.id
47
+ end
48
+ end
49
+ end
50
+
51
+ def sections_ids_hash
52
+ @sections_ids_hash ||= {}.with_indifferent_access
53
+ end
54
+
55
+ def components_ids_hash
56
+ @components_ids_hash ||= {}.with_indifferent_access
57
+ end
58
+
59
+ # Only store sections cache for the request duration to avoid expired
60
+ # references to AR objects between requests
61
+ #
62
+ def sections_cache
63
+ RequestStore.store[:sections_cache] ||= {}.with_indifferent_access
64
+ end
65
+
66
+ # Only store components cache for the request duration to avoid expired
67
+ # references to AR objects between requests
68
+ #
69
+ def components_cache
70
+ RequestStore.store[:components_cache] ||= {}.with_indifferent_access
71
+ end
72
+
73
+ def components_installed?
74
+ tables_exist = %w(component/base component_section).all? do |name|
75
+ Para.const_get(name.camelize).table_exists?
76
+ end
77
+
78
+ unless tables_exist
79
+ Rails.logger.warn(
80
+ "Para migrations are not installed.\n" \
81
+ "Skipping components definition until next app reload."
82
+ )
83
+ end
84
+
85
+ tables_exist
86
+ end
87
+
88
+ def eager_load_components!
89
+ $LOAD_PATH.each do |path|
90
+ next unless path.match(/\/components$/)
91
+
92
+ glob = File.join(path, '**', '*_component.rb')
93
+
94
+ Dir[glob].each do |file|
95
+ load(file)
96
+ end
97
+ end
98
+ end
99
+
100
+ class Section
101
+ attr_accessor :identifier, :model
102
+
103
+ def initialize(identifier, &block)
104
+ self.identifier = identifier.to_s
105
+ instance_eval(&block)
106
+ end
107
+
108
+ def component(*args)
109
+ components << Component.new(*args)
110
+ end
111
+
112
+ def components
113
+ @components ||= []
114
+ end
115
+
116
+ def refresh(attributes = {})
117
+ self.model = ComponentSection.where(identifier: identifier).first_or_initialize
118
+ model.assign_attributes(attributes)
119
+ model.save!
120
+
121
+ components.each_with_index do |component, index|
122
+ component.refresh(component_section: model, position: index)
123
+ end
124
+ end
125
+ end
126
+
127
+ class Component
128
+ attr_accessor :identifier, :type, :options, :model
129
+
130
+ def initialize(identifier, type, options = {})
131
+ self.identifier = identifier.to_s
132
+ self.type = Para::Component.registered_components[type]
133
+ self.options = options
134
+ end
135
+
136
+ def refresh(attributes = {})
137
+ self.model = type.where(identifier: identifier).first_or_initialize
138
+ model.assign_attributes(attributes.merge(options_with_defaults))
139
+ model.save!
140
+ end
141
+
142
+ # Ensures unset :configuration store options are set to nil to allow
143
+ # removing a configuration option from the components.rb file
144
+ #
145
+ def options_with_defaults
146
+ configurable_keys = type.local_stored_attributes.try(:[], :configuration) || []
147
+ configurable_keys += options.keys
148
+ configurable_keys.uniq!
149
+
150
+ configurable_keys.each_with_object({}) do |key, hash|
151
+ hash[key] = options[key]
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,21 @@
1
+ module Para
2
+ module Config
3
+ mattr_accessor :authenticate_admin_method
4
+ @@authenticate_admin_method = :authenticate_admin_user!
5
+
6
+ mattr_accessor :current_admin_method
7
+ @@current_admin_method = :current_admin_user
8
+
9
+ mattr_accessor :pagination_theme
10
+ @@pagination_theme = 'twitter-bootstrap-3'
11
+
12
+ mattr_accessor :admin_title
13
+ @@admin_title = nil
14
+
15
+ mattr_accessor :default_tree_max_depth
16
+ @@default_tree_max_depth = 3
17
+
18
+ mattr_accessor :resource_name_methods
19
+ @@resource_name_methods = [:name, :title]
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ module Para
2
+ class Engine < ::Rails::Engine
3
+ initializer 'add vendor path to assets pipeline' do |app|
4
+ %w(javascripts stylesheets).each do |folder|
5
+ app.config.assets.paths << File.expand_path(
6
+ "../../../vendor/assets/#{ folder }",
7
+ __FILE__
8
+ )
9
+ end
10
+ end
11
+
12
+ initializer 'Para precompile hook', group: :all do |app|
13
+ app.config.assets.precompile += %w(
14
+ para/admin.js
15
+ para/admin.css
16
+ )
17
+ end
18
+
19
+ initializer 'Para Orderable Mixin' do
20
+ ActiveSupport.on_load(:active_record) do
21
+ include Para::ActiveRecordOrderableMixin
22
+ end
23
+ end
24
+
25
+ initializer 'Para Cloneable' do
26
+ ActiveSupport.on_load(:active_record) do
27
+ include Para::Cloneable
28
+ end
29
+ end
30
+
31
+ initializer 'Extend paperclip attachment definition' do
32
+ return unless Kernel.const_defined?('Paperclip')
33
+
34
+ ActiveSupport.on_load(:active_record) do
35
+ ::Paperclip::HasAttachedFile.send(
36
+ :include, Para::Ext::Paperclip::HasAttachedFileMixin
37
+ )
38
+ end
39
+ end
40
+
41
+ initializer 'Extend cancan ControllerResource class' do
42
+ return unless Kernel.const_defined?('CanCan')
43
+
44
+ ActiveSupport.on_load(:active_record) do
45
+ ::CanCan::ControllerResource.send(
46
+ :include, Para::Ext::Cancan::ControllerResource
47
+ )
48
+ end
49
+ end
50
+
51
+ initializer 'Build components tree' do |app|
52
+ components_config_path = Rails.root.join('config', 'components.rb')
53
+
54
+ app.config.to_prepare do
55
+ require components_config_path if File.exist?(components_config_path)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ module Para
2
+ class BaseException < StandardError; end
3
+
4
+ class ComponentNotFound < BaseException
5
+ attr_accessor :type
6
+
7
+ def initialize(type)
8
+ @type = type
9
+ end
10
+
11
+ def message
12
+ "Component not found for type : #{ type }"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ module Para
2
+ module Exporter
3
+ class Base
4
+ attr_reader :resources
5
+
6
+ def initialize(resources)
7
+ @resources = resources
8
+ end
9
+
10
+ def disposition
11
+ 'inline'
12
+ end
13
+
14
+ def extension
15
+ raise '#extension must be defined to create the export file name'
16
+ end
17
+
18
+ def file_name
19
+ @file_name ||= [name, extension].join('.')
20
+ end
21
+
22
+ def self.register_base_exporter(type, exporter)
23
+ Exporter.base_exporters[type] = exporter
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'csv'
2
+
3
+ module Para
4
+ module Exporter
5
+ class Csv < Base
6
+ register_base_exporter :csv, self
7
+
8
+ def extension
9
+ 'csv'
10
+ end
11
+
12
+ def mime_type
13
+ 'text/csv'
14
+ end
15
+
16
+ def export_type
17
+ :excel
18
+ end
19
+
20
+ def render
21
+ CSV.generate do |csv|
22
+ csv << headers
23
+
24
+ resources.each do |resource|
25
+ csv << row_for(resource)
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def headers
33
+ fields.map do |field|
34
+ encode(User.human_attribute_name(field))
35
+ end
36
+ end
37
+
38
+ def row_for(resource)
39
+ fields.map do |field|
40
+ encode(resource.send(field))
41
+ end
42
+ end
43
+
44
+ def encode(string)
45
+ string.presence && string.to_s.encode('UTF-8')
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ module Para
2
+ module Exporter
3
+ class MissingExporterError < StandardError
4
+ attr_accessor :model_name, :format, :exporter_name
5
+
6
+ def initialize(model_name, format, exporter_name)
7
+ @model_name = model_name
8
+ @format = format
9
+ @exporter_name = exporter_name
10
+ end
11
+
12
+ def message
13
+ "No exporter found for model \"#{ model_name }\" and format " +
14
+ "\"#{ format }\". Please create the #{ exporter_name } class " +
15
+ "manually or with the following command : " +
16
+ "`rails g para:exporter #{ model_name.underscore } #{ format }"
17
+ end
18
+ end
19
+
20
+ def self.for(model_name, format)
21
+ exporter_name = name_for(model_name, format)
22
+
23
+ begin
24
+ const_get(exporter_name)
25
+ rescue NameError => e
26
+ if e.message == "uninitialized constant Para::Exporter::#{ exporter_name }"
27
+ raise MissingExporterError.new(model_name, format, exporter_name)
28
+ else
29
+ raise e
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.name_for(model_name, format)
35
+ [
36
+ '',
37
+ format_exporter_name(format),
38
+ model_exporter_name(model_name)
39
+ ].join('::')
40
+ end
41
+
42
+ def self.model_exporter_name(model_name)
43
+ [model_name.to_s.pluralize, 'Exporter'].join
44
+ end
45
+
46
+ def self.format_exporter_name(format)
47
+ format.to_s.camelize
48
+ end
49
+
50
+ def self.base_exporters
51
+ @base_exporters ||= {}.with_indifferent_access
52
+ end
53
+ end
54
+ end
55
+
56
+ require 'para/exporter/base'
57
+ require 'para/exporter/csv'
@@ -0,0 +1,26 @@
1
+ module Para
2
+ module Ext
3
+ module Cancan
4
+ module ControllerResource
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :assign_attributes, :parent_missing_management
9
+ end
10
+
11
+ # Todo : Document why this extension was added ?
12
+ def assign_attributes_with_parent_missing_management(resource)
13
+ if @options[:singleton] && parent_resource && resource.respond_to?(:"#{ parent_name }=")
14
+ resource.send(:"#{ parent_name }=", parent_resource)
15
+ end
16
+
17
+ initial_attributes.each do |attr_name, value|
18
+ resource.send(:"#{ attr_name }=", value)
19
+ end
20
+
21
+ resource
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ module Para
2
+ module Ext
3
+ module Paperclip
4
+ module HasAttachedFileMixin
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :define, :removeable_management
9
+ end
10
+
11
+ def define_with_removeable_management
12
+ define_without_removeable_management
13
+ define_removeable
14
+ end
15
+
16
+ private
17
+
18
+ def define_removeable
19
+ return if @options[:removeable] == false
20
+
21
+ attachment_name = @name
22
+ klass = @klass
23
+ # Define the setter to remove the attachment
24
+ #
25
+ @klass.send :define_method, :"remove_#{ attachment_name }=" do |value|
26
+ if value == '1'
27
+ removed_attachments << attachment_name
28
+
29
+ # Notify ActiveRecord that the model has changed so nested models
30
+ # get to run validation hooks and attachments are cleared
31
+ send(:"#{ attachment_name }_file_name_will_change!")
32
+ end
33
+ end
34
+
35
+ @klass.send :define_method, :"remove_#{ attachment_name }" do |value|
36
+ removed_attachments.include?(attachment_name) ? '1' : nil
37
+ end
38
+
39
+ # Lazy method initialization on attachment target class
40
+ #
41
+ unless @klass.method_defined?(:removed_attachments)
42
+ # List of all removed attachments for the current instance
43
+ #
44
+ @klass.send :define_method, :removed_attachments do
45
+ @removed_attachments ||= []
46
+ end
47
+
48
+ # Define before validation hook to clear removed attachments before
49
+ # the instance is validated
50
+ #
51
+ @klass.send :define_method, :clear_removed_attachments do
52
+ removed_attachments.each do |name|
53
+ if send(:"#{ name }?")
54
+ send(name).clear
55
+ end
56
+ end
57
+
58
+ removed_attachments.clear
59
+ end
60
+
61
+ @klass.send(:before_validation, :clear_removed_attachments)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
data/lib/para/ext.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Para
2
+ # References all framework extensions to make our code work well with other
3
+ # libraries.
4
+ #
5
+ # One goal is to have the minimum of them, but sometimes, without them, we
6
+ # may need to make our own code dirty
7
+ #
8
+ module Ext
9
+ end
10
+ end
11
+
12
+ require 'para/ext/cancan'
13
+ require 'para/ext/paperclip'
@@ -0,0 +1,70 @@
1
+ module Para
2
+ module FormBuilder
3
+ module Containers
4
+ def fieldset(&block)
5
+ template.content_tag(:div, class: 'block form-inputs') do
6
+ template.capture(&block)
7
+ end
8
+ end
9
+
10
+ def actions(options = {}, &block)
11
+ template.content_tag(:div, class: 'block form-actions form-group') do
12
+ template.content_tag(:div, class: 'col-sm-9 col-sm-offset-3') do
13
+ if block
14
+ template.capture(&block)
15
+ else
16
+ if options.empty?
17
+ options[:only] = template.instance_variable_get(:@component).default_form_actions
18
+ end
19
+ actions_buttons_for(options).join("\n").html_safe
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def actions_buttons_for(options)
26
+ names = [:submit, :submit_and_edit, :submit_and_add_another, :cancel]
27
+
28
+ names.select! { |name| Array.wrap(options[:only]).include?(name) } if options[:only]
29
+ names.reject! { |name| Array.wrap(options[:except]).include?(name) } if options[:except]
30
+ buttons = names.map { |name| send(:"para_#{ name }_button") }
31
+ buttons.unshift(return_to_hidden_field)
32
+ buttons
33
+ end
34
+
35
+ def return_to_hidden_field
36
+ template.hidden_field_tag(:return_to, return_to_path)
37
+ end
38
+
39
+ def para_submit_button(options = {})
40
+ button(:submit, I18n.t('para.shared.save'), name: '_save', class: 'btn-success')
41
+ end
42
+
43
+ def para_submit_and_edit_button
44
+ button(:submit, I18n.t('para.shared.save_and_edit'), name: '_save_and_edit', class: 'btn-primary')
45
+ end
46
+
47
+ def para_submit_and_add_another_button
48
+ button(:submit, I18n.t('para.shared.save_and_add_another_button'), name: '_save_and_add_another', class: 'btn-primary')
49
+ end
50
+
51
+ def para_cancel_button
52
+ template.link_to(
53
+ I18n.t('para.shared.cancel'),
54
+ return_to_path,
55
+ class: 'btn btn-danger'
56
+ )
57
+ end
58
+
59
+ def return_to_path
60
+ template.params[:return_to].presence || component_path
61
+ end
62
+
63
+ def component_path
64
+ if (component = template.instance_variable_get(:@component))
65
+ component.path
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,9 @@
1
+ module Para
2
+ module FormBuilder
3
+ module FieldMappings
4
+ def fields
5
+ @fields ||= template.model_field_mappings(object.class).fields
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,80 @@
1
+ module Para
2
+ module FormBuilder
3
+ module NestedForm
4
+ def nested_fields
5
+ @nested_fields ||= fields.reject do |field|
6
+ inverse_of?(field.name)
7
+ end
8
+ end
9
+
10
+ def nested_resource_name
11
+ @nested_resource_name ||= begin
12
+ name_method = Para.config.resource_name_methods.find do |method_name|
13
+ object.respond_to?(method_name)
14
+ end
15
+
16
+ (name_method && object.try(name_method).presence) || default_resource_name
17
+ end
18
+ end
19
+
20
+ def nested_resource_dom_id
21
+ return "" unless nested?
22
+
23
+ @nested_resource_dom_id ||= [
24
+ object.class.model_name.singular,
25
+ (Time.now.to_f * 1000).to_i,
26
+ (object.id || "_new_#{ nested_attribute_name }_id")
27
+ ].join('-')
28
+ end
29
+
30
+ def remove_association_button
31
+ return "" unless allow_destroy?
32
+
33
+ template.content_tag(:div, class: 'panel-btns pull-right') do
34
+ template.link_to_remove_association(
35
+ self, wrapper_class: 'form-fields', class: 'btn btn-danger'
36
+ ) do
37
+ template.content_tag(:i, '', class: 'fa fa-trash') +
38
+ I18n.t('para.form.nested.remove')
39
+ end
40
+ end
41
+ end
42
+
43
+ def allow_destroy?
44
+ return false unless nested?
45
+
46
+ nested_options = parent_object.nested_attributes_options
47
+ relation = nested_options[nested_attribute_name]
48
+ relation && relation[:allow_destroy]
49
+ end
50
+
51
+ def inverse_of?(field_name)
52
+ return false unless nested?
53
+
54
+ reflection = parent_object.class.reflect_on_association(nested_attribute_name)
55
+ reflection && (reflection.options[:inverse_of] == field_name)
56
+ end
57
+
58
+ def nested?
59
+ nested_attribute_name.present?
60
+ end
61
+
62
+ def nested_attribute_name
63
+ options[:nested_attribute_name]
64
+ end
65
+
66
+ def parent_object
67
+ nested? && options[:parent_builder] && options[:parent_builder].object
68
+ end
69
+
70
+ private
71
+
72
+ def default_resource_name
73
+ model_name = object.class.model_name.human
74
+ id_or_new = (id = object.id) ? id : I18n.t('para.form.nested.new')
75
+
76
+ [model_name, id_or_new].join(' - ')
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ module Para
2
+ module FormBuilder
3
+ module Ordering
4
+ def orderable?
5
+ options.fetch(:orderable, object.class.orderable?)
6
+ end
7
+
8
+ def reorder_anchor(options = {})
9
+ return "" unless orderable?
10
+ options[:form] = self
11
+ template.reorder_anchor(options)
12
+ end
13
+ end
14
+ end
15
+ end