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,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