para 0.6.9 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/para/admin/tangram-mosaic.png +0 -0
  3. data/app/assets/images/para/admin/template-section.svg +23 -0
  4. data/app/assets/javascripts/para/admin.coffee +20 -0
  5. data/app/assets/javascripts/para/admin/tabs.coffee +22 -5
  6. data/app/assets/javascripts/para/admin/theme_actions.coffee +3 -3
  7. data/app/assets/javascripts/para/inputs/material-input.coffee +4 -0
  8. data/app/assets/javascripts/para/inputs/multi-select-input.coffee +173 -0
  9. data/app/assets/javascripts/para/inputs/nested_many.coffee +9 -9
  10. data/app/assets/stylesheets/para/admin.sass +5 -5
  11. data/app/assets/stylesheets/para/admin/main.sass +32 -0
  12. data/app/assets/stylesheets/para/admin/src/_alert.sass +17 -0
  13. data/app/assets/stylesheets/para/admin/src/_base.sass +51 -0
  14. data/app/assets/stylesheets/para/admin/src/_bootstrap-variables.scss +874 -0
  15. data/app/assets/stylesheets/para/admin/src/_breadcrumb.sass +22 -0
  16. data/app/assets/stylesheets/para/admin/src/_buttons.sass +107 -0
  17. data/app/assets/stylesheets/para/admin/src/_checkable.sass +94 -0
  18. data/app/assets/stylesheets/para/admin/src/_common.sass +203 -0
  19. data/app/assets/stylesheets/para/admin/src/_dropdown.sass +40 -0
  20. data/app/assets/stylesheets/para/admin/src/_form.sass +271 -0
  21. data/app/assets/stylesheets/para/admin/src/_list.sass +61 -0
  22. data/app/assets/stylesheets/para/admin/src/_mixins.sass +113 -0
  23. data/app/assets/stylesheets/para/admin/src/_multi-select.sass +91 -0
  24. data/app/assets/stylesheets/para/admin/src/_navigation.sass +150 -0
  25. data/app/assets/stylesheets/para/admin/src/_navtabs.sass +41 -0
  26. data/app/assets/stylesheets/para/admin/src/_nested-many.sass +69 -0
  27. data/app/assets/stylesheets/para/admin/src/_nested_one.sass +13 -0
  28. data/app/assets/stylesheets/para/admin/src/_orderable.sass +44 -0
  29. data/app/assets/stylesheets/para/admin/src/_page-loading.sass +39 -0
  30. data/app/assets/stylesheets/para/admin/src/_pagination.sass +34 -0
  31. data/app/assets/stylesheets/para/admin/src/_panel.sass +55 -0
  32. data/app/assets/stylesheets/para/admin/src/_responsive.sass +100 -0
  33. data/app/assets/stylesheets/para/admin/{theme → src}/_sorting.sass +11 -3
  34. data/app/assets/stylesheets/para/admin/src/_statcard.sass +41 -0
  35. data/app/assets/stylesheets/para/admin/src/_table.sass +33 -0
  36. data/app/assets/stylesheets/para/admin/src/_tree.sass +52 -0
  37. data/app/assets/stylesheets/para/admin/src/_variables.sass +37 -0
  38. data/app/assets/stylesheets/para/admin/src/_well.sass +12 -0
  39. data/app/assets/stylesheets/para/{admin/src → lib}/datetimepicker.sass +1 -3
  40. data/app/assets/stylesheets/para/{admin/src → lib}/fuelux.sass +3 -5
  41. data/app/assets/stylesheets/para/lib/jasny-bootstrap.sass +97 -0
  42. data/app/assets/stylesheets/para/{admin/src → lib}/redactor.sass +2 -5
  43. data/app/assets/stylesheets/para/lib/selectize.sass +112 -0
  44. data/app/assets/stylesheets/para/lib/slider.sass +12 -0
  45. data/app/controllers/concerns/para/admin/resource_controller_concerns.rb +14 -2
  46. data/app/controllers/para/admin/base_controller.rb +7 -0
  47. data/app/controllers/para/admin/crud_resources_controller.rb +13 -6
  48. data/app/controllers/para/admin/form_resources_controller.rb +5 -3
  49. data/app/controllers/para/admin/resources_controller.rb +9 -3
  50. data/app/controllers/para/admin/search_controller.rb +15 -0
  51. data/app/controllers/para/application_controller.rb +10 -2
  52. data/app/helpers/para/admin/page_helper.rb +9 -9
  53. data/app/helpers/para/application_helper.rb +1 -10
  54. data/app/helpers/para/form_helper.rb +5 -6
  55. data/app/helpers/para/model_helper.rb +13 -3
  56. data/app/helpers/para/ordering_helper.rb +1 -1
  57. data/app/helpers/para/search_results_helper.rb +16 -0
  58. data/app/helpers/para/tree_helper.rb +10 -6
  59. data/app/models/para/page.rb +5 -0
  60. data/app/models/para/page/section.rb +42 -0
  61. data/app/models/para/page/section_resource.rb +12 -0
  62. data/app/views/layouts/para/admin.html.haml +5 -8
  63. data/app/views/para/admin/dashboard.html.haml +21 -8
  64. data/app/views/para/admin/form_resources/show.html.haml +1 -1
  65. data/app/views/para/admin/imports/new.html.haml +2 -2
  66. data/app/views/para/admin/resources/_actions.html.haml +3 -3
  67. data/app/views/para/admin/resources/_add_button.html.haml +1 -1
  68. data/app/views/para/admin/resources/_exports_menu.html.haml +6 -6
  69. data/app/views/para/admin/resources/_filters.html.haml +7 -8
  70. data/app/views/para/admin/resources/_imports_menu.html.haml +6 -6
  71. data/app/views/para/admin/resources/_list.html.haml +14 -15
  72. data/app/views/para/admin/resources/_per_page_select.html.haml +4 -3
  73. data/app/views/para/admin/resources/_subclassable_add_button.html.haml +3 -3
  74. data/app/views/para/admin/resources/_tree.html.haml +7 -9
  75. data/app/views/para/admin/resources/_tree_item.html.haml +5 -6
  76. data/app/views/para/admin/resources/edit.html.haml +1 -1
  77. data/app/views/para/admin/resources/new.html.haml +1 -1
  78. data/app/views/para/admin/search/_result.html.haml +7 -0
  79. data/app/views/para/admin/search/index.html.haml +1 -0
  80. data/app/views/para/admin/settings_component/show.html.haml +1 -1
  81. data/app/views/para/admin/shared/_breadcrumbs.html.haml +2 -3
  82. data/app/views/para/admin/shared/_header.html.haml +16 -27
  83. data/app/views/para/admin/shared/_navigation.html.haml +20 -19
  84. data/app/views/para/form/_tabs.html.haml +6 -7
  85. data/app/views/para/inputs/_multi_select.html.haml +40 -0
  86. data/app/views/para/inputs/_nested_many.html.haml +4 -4
  87. data/app/views/para/inputs/multi_select/_no_items.html.haml +6 -0
  88. data/app/views/para/inputs/nested_many/_add.html.haml +1 -2
  89. data/app/views/para/inputs/nested_many/_add_with_subclasses.html.haml +4 -5
  90. data/app/views/para/inputs/nested_many/_container.html.haml +7 -8
  91. data/app/views/shared/_breadcrumbs.html.haml +4 -0
  92. data/config/locales/fr.yml +29 -4
  93. data/db/migrate/20170324125547_create_para_page_section_resources.rb +15 -0
  94. data/lib/generators/para/filters/templates/_filters.html.haml +3 -5
  95. data/lib/generators/para/install/templates/initializer.rb +12 -0
  96. data/lib/generators/para/page/section/section_generator.rb +13 -2
  97. data/lib/generators/para/page/section/templates/section.html.haml.erb +3 -0
  98. data/lib/generators/para/resource/resource_generator.rb +1 -1
  99. data/lib/para.rb +25 -21
  100. data/lib/para/attribute_field.rb +2 -0
  101. data/lib/para/attribute_field/base.rb +1 -1
  102. data/lib/para/attribute_field/belongs_to.rb +1 -1
  103. data/lib/para/attribute_field/boolean.rb +1 -1
  104. data/lib/para/attribute_field/friendly_id.rb +19 -0
  105. data/lib/para/attribute_field/has_many.rb +43 -4
  106. data/lib/para/attribute_field/nested_field.rb +46 -0
  107. data/lib/para/attribute_field/nested_many.rb +30 -9
  108. data/lib/para/attribute_field/nested_one.rb +28 -9
  109. data/lib/para/attribute_field/password.rb +1 -1
  110. data/lib/para/attribute_field/relation.rb +19 -1
  111. data/lib/para/attribute_field_mappings.rb +9 -3
  112. data/lib/para/breadcrumbs/breadcrumb.rb +57 -10
  113. data/lib/para/breadcrumbs/controller.rb +19 -6
  114. data/lib/para/breadcrumbs/manager.rb +9 -1
  115. data/lib/para/config.rb +3 -0
  116. data/lib/para/controller_resource.rb +34 -0
  117. data/lib/para/engine.rb +33 -14
  118. data/lib/para/exporter/base.rb +1 -1
  119. data/lib/para/ext.rb +1 -1
  120. data/lib/para/ext/active_record_nested_attributes.rb +51 -0
  121. data/lib/para/ext/paperclip.rb +2 -6
  122. data/lib/para/form_builder.rb +3 -0
  123. data/lib/para/form_builder/attributes_mappings_tracker.rb +45 -0
  124. data/lib/para/form_builder/containers.rb +13 -17
  125. data/lib/para/form_builder/nested_form.rb +2 -3
  126. data/lib/para/helpers.rb +8 -0
  127. data/lib/para/helpers/attributes_mappings.rb +13 -0
  128. data/lib/para/helpers/resource_name.rb +14 -0
  129. data/lib/para/inputs.rb +1 -0
  130. data/lib/para/inputs/multi_select_input.rb +90 -0
  131. data/lib/para/inputs/nested_many_input.rb +6 -1
  132. data/lib/para/markup/resources_table.rb +15 -11
  133. data/lib/para/model_field_parsers.rb +3 -2
  134. data/lib/para/model_field_parsers/base.rb +7 -2
  135. data/lib/para/model_field_parsers/friendly_id.rb +19 -0
  136. data/lib/para/model_field_parsers/relations.rb +20 -1
  137. data/lib/para/page/model.rb +3 -1
  138. data/lib/para/routes.rb +1 -0
  139. data/lib/para/simple_form_config.rb +85 -0
  140. data/lib/para/version.rb +1 -1
  141. data/lib/rails/routing_mapper.rb +5 -8
  142. data/vendor/assets/javascripts/jasny-bootstrap.js +577 -0
  143. data/vendor/assets/stylesheets/animate.css +3340 -0
  144. data/vendor/assets/stylesheets/hint.css +649 -0
  145. data/vendor/assets/stylesheets/jasny-bootstrap.css +287 -0
  146. metadata +114 -67
  147. data/app/assets/images/para/admin/bg.png +0 -0
  148. data/app/assets/javascripts/para/admin.js +0 -20
  149. data/app/assets/stylesheets/admin/theme.sass +0 -0
  150. data/app/assets/stylesheets/para/admin/src/jasny.bootstrap.sass +0 -40
  151. data/app/assets/stylesheets/para/admin/src/nested_many.sass +0 -5
  152. data/app/assets/stylesheets/para/admin/src/page-loading.sass +0 -24
  153. data/app/assets/stylesheets/para/admin/src/page-top-bar.sass +0 -9
  154. data/app/assets/stylesheets/para/admin/src/selectize.sass +0 -46
  155. data/app/assets/stylesheets/para/admin/src/slider.sass +0 -12
  156. data/app/assets/stylesheets/para/admin/theme.sass +0 -22
  157. data/app/assets/stylesheets/para/admin/theme/_base.sass +0 -176
  158. data/app/assets/stylesheets/para/admin/theme/_breadcrumb.sass +0 -75
  159. data/app/assets/stylesheets/para/admin/theme/_buttons.sass +0 -27
  160. data/app/assets/stylesheets/para/admin/theme/_checkable.sass +0 -108
  161. data/app/assets/stylesheets/para/admin/theme/_commonds.sass +0 -210
  162. data/app/assets/stylesheets/para/admin/theme/_dropdown.sass +0 -52
  163. data/app/assets/stylesheets/para/admin/theme/_form.sass +0 -150
  164. data/app/assets/stylesheets/para/admin/theme/_list.sass +0 -139
  165. data/app/assets/stylesheets/para/admin/theme/_navigation.sass +0 -229
  166. data/app/assets/stylesheets/para/admin/theme/_navtabs.sass +0 -151
  167. data/app/assets/stylesheets/para/admin/theme/_orderable.sass +0 -45
  168. data/app/assets/stylesheets/para/admin/theme/_panel.sass +0 -264
  169. data/app/assets/stylesheets/para/admin/theme/_responsive.sass +0 -123
  170. data/app/assets/stylesheets/para/admin/theme/_table.sass +0 -28
  171. data/app/assets/stylesheets/para/admin/theme/_tree.sass +0 -74
  172. data/app/assets/stylesheets/para/admin/theme/_variables.sass +0 -31
  173. data/app/assets/stylesheets/para/application.css +0 -15
  174. data/app/assets/stylesheets/para/lib/_variables.scss +0 -646
  175. data/app/assets/stylesheets/para/overrides/responsive.sass +0 -62
  176. data/app/assets/stylesheets/para/overrides/theme.sass +0 -172
  177. data/lib/para/ext/cancan.rb +0 -26
  178. data/vendor/assets/javascripts/jasny.bootstrap.min.js +0 -7
  179. data/vendor/assets/stylesheets/animate.min.css +0 -6
  180. data/vendor/assets/stylesheets/jasny.bootstrap.min.css +0 -7
@@ -19,7 +19,7 @@ module Para
19
19
  reflection.klass.all
20
20
  end
21
21
 
22
- def parse_input(params)
22
+ def parse_input(params, resource)
23
23
  if (id = params[reflection.foreign_key].presence) && !reflection.klass.exists?(id: id)
24
24
  on_the_fly_creation(id) do |resource|
25
25
  params[reflection.foreign_key] = resource.id
@@ -10,7 +10,7 @@ module Para
10
10
  end
11
11
 
12
12
  def wrapper_name
13
- :horizontal_radio_and_checkboxes
13
+ :vertical_radio_and_checkboxes
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,19 @@
1
+ module Para
2
+ module AttributeField
3
+ class FriendlyId < Base
4
+ register :friendly_id, self
5
+
6
+ # Set empty string as nil to allow default friendly id methods to
7
+ # generate the slug when the field is empty
8
+ def parse_input(params, resource)
9
+ params[slug_column] = nil if slug_column && params[slug_column] == ''
10
+ end
11
+
12
+ private
13
+
14
+ def slug_column
15
+ model.try(:friendly_id_config).try(:slug_column)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -13,21 +13,60 @@ module Para
13
13
  end.join(', ')
14
14
  end
15
15
 
16
- def parse_input(params)
16
+ def parse_input(params, resource)
17
17
  if (ids = params[plural_foreign_key].presence) && String === ids
18
18
  # Format selectize value for Rails
19
- ids = params[plural_foreign_key] = ids.split(',')
19
+ ids = params[plural_foreign_key] = ids.split(',').map(&:to_i)
20
20
 
21
- on_the_fly_creation(ids) do |resource, value|
21
+ on_the_fly_creation(ids) do |res, value|
22
22
  params[plural_foreign_key].delete(value)
23
- params[plural_foreign_key] << resource.id
23
+ params[plural_foreign_key] << res.id
24
24
  end
25
25
  end
26
+
27
+ assign_ids(params, resource)
26
28
  end
27
29
 
28
30
  def plural_foreign_key
29
31
  foreign_key.to_s.pluralize
30
32
  end
33
+
34
+ def assign_ids(params, resource)
35
+ return unless Array === params[plural_foreign_key]
36
+
37
+ ids = params.delete(plural_foreign_key)
38
+
39
+ if through_reflection && through_reflection.klass.orderable?
40
+ assign_ordered_through_reflection_ids(through_reflection, resource, ids)
41
+ else
42
+ resource.assign_attributes(plural_foreign_key => ids)
43
+ end
44
+ end
45
+
46
+ def assign_ordered_through_reflection_ids(through_reflection, resource, ids)
47
+ association = resource.association(through_reflection.name)
48
+ join_resources = association.load_target
49
+
50
+ return association.replace([]) if ids.empty?
51
+
52
+ new_resources = ids.each_with_index.map do |id, position|
53
+ join_resource = join_resources.find do |res|
54
+ res.send(through_relation_source_foreign_key) == id &&
55
+ (!polymorphic_through_reflection? || res.send(reflection.foreign_type) == reflection.klass.name)
56
+ end
57
+
58
+ unless join_resource
59
+ attributes = { through_relation_source_foreign_key => id }
60
+ attributes[reflection.foreign_type] = reflection.klass.name if polymorphic_through_reflection?
61
+ join_resource = association.build(attributes)
62
+ end
63
+
64
+ join_resource.position = position
65
+ join_resource
66
+ end
67
+
68
+ association.replace(new_resources)
69
+ end
31
70
  end
32
71
  end
33
72
  end
@@ -0,0 +1,46 @@
1
+ module Para
2
+ module AttributeField
3
+ module NestedField
4
+ def nested_model_mappings(nested_attributes)
5
+ model = if (type = nested_attributes[:type]).present?
6
+ nested_attributes[:type].try(:constantize)
7
+ else
8
+ reflection.klass
9
+ end
10
+
11
+ mappings = attributes_mappings_for(nested_attributes)
12
+
13
+ AttributeFieldMappings.new(model, mappings: mappings)
14
+ end
15
+
16
+ def nested_attributes_key
17
+ @nested_attributes_key ||= :"#{ name }_attributes"
18
+ end
19
+
20
+ # This method extends the current resource so its #id method returns a
21
+ # fake id based on the given attributes, but immediately returns to its
22
+ # standard behavior of returning nil as soon as the `#assign_attributes`
23
+ # method is called.
24
+ #
25
+ # During nested attributes assignation, the id of the resource is used
26
+ # to assign it its nested params. When it is nil, a new resource is
27
+ # created, but since we already need our resource to be created at the
28
+ # time we parse the input params, we fake the presence of an id while the
29
+ # nested attributes assignation is running, and remove that behavior as
30
+ # soon as we don't need it anymore.
31
+ #
32
+ def temporarily_extend_new_resource(resource, attributes)
33
+ resource.instance_variable_set(:@_waiting_for_attributes_assignation, true)
34
+
35
+ resource.define_singleton_method(:assign_attributes) do |*args|
36
+ @_waiting_for_attributes_assignation = false
37
+ super(*args)
38
+ end
39
+
40
+ resource.define_singleton_method(:id) do
41
+ @_waiting_for_attributes_assignation ? attributes['id'] : read_attribute(:id)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,28 +1,49 @@
1
1
  module Para
2
2
  module AttributeField
3
3
  class NestedManyField < AttributeField::HasManyField
4
+ include Para::Helpers::AttributesMappings
5
+ include Para::AttributeField::NestedField
6
+
4
7
  register :nested_many, self
5
8
 
6
- def parse_input(params)
9
+ def parse_input(params, resource)
7
10
  if (nested_attributes = params[nested_attributes_key])
8
11
  nested_attributes.each do |index, attributes|
9
- nested_model_mappings.fields.each do |field|
10
- field.parse_input(attributes)
12
+ mappings = nested_model_mappings(attributes)
13
+ nested_resource = fetch_or_build_nested_resource_for(resource, index, attributes)
14
+
15
+ mappings.fields.each do |field|
16
+ field.parse_input(attributes, nested_resource)
11
17
  end
12
18
 
13
19
  params[nested_attributes_key][index] = attributes
14
20
  end
15
21
  else
16
- super(params)
22
+ super
17
23
  end
18
24
  end
19
25
 
20
- def nested_model_mappings
21
- @nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
22
- end
26
+ private
27
+
28
+ # Force loading association and look for a resource matching the provided
29
+ # attributes. If no resource is found, one is created and a fake `id` is
30
+ # assigned to it to hack Rails' nested resources lookup from new
31
+ # attributes params.
32
+ #
33
+ # This is necessary to be able to provide a resource to the #parse_input
34
+ # method when called on the nested resources hash
35
+ #
36
+ def fetch_or_build_nested_resource_for(parent, index, attributes)
37
+ nested_resources = parent.association(name).load_target
23
38
 
24
- def nested_attributes_key
25
- @nested_attributes_key ||= :"#{ name }_attributes"
39
+ if (id = attributes['id'].presence)
40
+ nested_resources.find { |res| res.id == id.to_i }
41
+ else
42
+ parent.association(name).build(attributes.slice('type')).tap do |resource|
43
+ attributes['id'] = "__#{ index }"
44
+ temporarily_extend_new_resource(resource, attributes)
45
+ end
46
+ end
26
47
  end
27
48
  end
28
49
  end
@@ -1,26 +1,45 @@
1
1
  module Para
2
2
  module AttributeField
3
3
  class NestedOneField < AttributeField::BelongsToField
4
+ include Para::Helpers::AttributesMappings
5
+ include Para::AttributeField::NestedField
6
+
4
7
  register :nested_one, self
5
8
 
6
- def parse_input(params)
9
+ def parse_input(params, resource)
7
10
  if (nested_attributes = params[nested_attributes_key])
8
- nested_model_mappings.fields.each do |field|
9
- field.parse_input(nested_attributes)
11
+ mappings = nested_model_mappings(nested_attributes)
12
+ nested_resource = fetch_or_build_nested_resource_for(resource, nested_attributes)
13
+
14
+ mappings.fields.each do |field|
15
+ field.parse_input(nested_attributes, nested_resource)
10
16
  end
11
17
 
12
18
  params[nested_attributes_key] = nested_attributes
13
19
  else
14
- super(params)
20
+ super
15
21
  end
16
22
  end
17
23
 
18
- def nested_model_mappings
19
- @nested_model_mappings ||= AttributeFieldMappings.new(reflection.klass)
20
- end
24
+ private
21
25
 
22
- def nested_attributes_key
23
- @nested_attributes_key ||= :"#{ name }_attributes"
26
+ # Force loading association and look for a resource matching the provided
27
+ # attributes. If no resource is found, one is created and a fake `id` is
28
+ # assigned to it to hack Rails' nested resources lookup from new
29
+ # attributes params.
30
+ #
31
+ # This is necessary to be able to provide a resource to the #parse_input
32
+ # method when called on the nested resources hash
33
+ #
34
+ def fetch_or_build_nested_resource_for(parent, attributes)
35
+ if (nested_resource = parent.association(name).load_target)
36
+ return nested_resource
37
+ end
38
+
39
+ parent.association(name).build(attributes.slice('type')).tap do |resource|
40
+ attributes['id'] = "__#{ object_id }"
41
+ temporarily_extend_new_resource(resource, attributes)
42
+ end
24
43
  end
25
44
  end
26
45
  end
@@ -12,7 +12,7 @@ module Para
12
12
  nil
13
13
  end
14
14
 
15
- def parse_input(params)
15
+ def parse_input(params, resource)
16
16
  params[name] = params[name].presence
17
17
  end
18
18
  end
@@ -1,7 +1,7 @@
1
1
  module Para
2
2
  module AttributeField
3
3
  class RelationField < AttributeField::Base
4
- private
4
+ include Para::Helpers::ResourceName
5
5
 
6
6
  def reflection
7
7
  @reflection ||= model.reflect_on_association(name)
@@ -15,6 +15,24 @@ module Para
15
15
  end
16
16
  end
17
17
 
18
+ def through_relation
19
+ @through_relation ||= reflection.options[:through]
20
+ end
21
+
22
+ def through_reflection
23
+ @through_reflection ||= through_relation && model.reflect_on_association(through_relation)
24
+ end
25
+
26
+ def through_relation_source_foreign_key
27
+ @through_relation_source_foreign_key ||= reflection.source_reflection.foreign_key
28
+ end
29
+
30
+ def polymorphic_through_reflection?
31
+ !!(through_relation && reflection.source_reflection.options[:polymorphic])
32
+ end
33
+
34
+ private
35
+
18
36
  def resource_name(resource)
19
37
  Para.config.resource_name_methods.each do |method|
20
38
  return resource.send(method) if resource.respond_to?(method)
@@ -2,11 +2,17 @@ module Para
2
2
  class AttributeFieldMappings
3
3
  UNEDITABLE_ATTRIBUTES = %w(id component_id created_at updated_at type)
4
4
 
5
- attr_reader :model, :fields_hash, :whitelist_attributes
5
+ attr_reader :model, :fields_hash, :whitelist_attributes, :mappings
6
6
 
7
- def initialize(model, whitelist_attributes: nil)
7
+ # The mappings hash is a form provided hash of attributes and input types
8
+ # mapping that are automatically generated in the form builder and that
9
+ # allows our model field parsers to spot unregistered fields and process
10
+ # them when needed.
11
+ #
12
+ def initialize(model, whitelist_attributes: nil, mappings: nil)
8
13
  @model = model
9
14
  @whitelist_attributes = whitelist_attributes
15
+ @mappings = mappings || {}
10
16
 
11
17
  process_fields!
12
18
  end
@@ -52,7 +58,7 @@ module Para
52
58
  end
53
59
  end.with_indifferent_access
54
60
 
55
- Para::ModelFieldParsers.parse!(model, fields_hash)
61
+ Para::ModelFieldParsers.parse!(model, fields_hash, mappings)
56
62
  end
57
63
 
58
64
  def build_field_for(attribute_name, type)
@@ -1,42 +1,89 @@
1
1
  module Para
2
2
  module Breadcrumbs
3
3
  class Breadcrumb
4
- attr_reader :identifier, :_path, :options
4
+ include ActionDispatch::Routing::PolymorphicRoutes
5
+ include Para::Helpers::ResourceName
5
6
 
6
- def initialize(identifier, path, *options)
7
- @identifier = identifier
7
+ attr_reader :resource_or_identifier, :_path, :controller, :options
8
+
9
+ def initialize(resource_or_identifier, path, controller, *options)
10
+ @resource_or_identifier = resource_or_identifier
8
11
  @_path = path
12
+ @controller = controller
9
13
  @options = options
10
14
  end
11
15
 
12
16
  def title
13
- @title ||= if Symbol === identifier
14
- ::I18n.t("admin.breadcrumbs.#{ identifier }")
17
+ @title ||= if resource_or_identifier.is_a?(Symbol)
18
+ translate(resource_or_identifier)
19
+ elsif resource_or_identifier.is_a?(ActiveRecord::Base)
20
+ resource_name(resource_or_identifier)
15
21
  else
16
- identifier
22
+ resource_or_identifier
17
23
  end
18
24
  end
19
25
 
20
26
  # Allow lazy evaluation of routes to define breadcrumbs before being
21
27
  # able to access request or routes
28
+ #
22
29
  def path
23
- @path ||= if Symbol === _path
30
+ @path ||= if _path.is_a?(Symbol)
24
31
  find_route_for(_path, *options)
25
32
  elsif _path
26
33
  _path
27
34
  else
28
- '#'
35
+ begin
36
+ polymorphic_path(resource_or_identifier)
37
+ rescue
38
+ '#'
39
+ end
29
40
  end
30
41
  end
31
42
 
32
43
  def active?(request)
33
- path == request.path || !_path
44
+ path == request.path || path == '#'
34
45
  end
35
46
 
36
47
  private
37
48
 
49
+ def translate(key)
50
+ if controller.admin?
51
+ ::I18n.t("admin.breadcrumbs.#{ resource_or_identifier }")
52
+ else
53
+ # Check if a specific translation has been defined
54
+ if (translation = ::I18n.t("breadcrumbs.#{ resource_or_identifier }", default: '')).present?
55
+ translation
56
+ else
57
+ # If no translation is defined, we check if there's a plural model
58
+ # translation associated to the given key
59
+ begin
60
+ klass = key.to_s.singularize.camelize.constantize
61
+ klass.try(:model_name).try(:human, count: 2) || key
62
+ rescue NameError
63
+ key
64
+ end
65
+ end
66
+ end
67
+ end
68
+
38
69
  def find_route_for(path, *options)
39
- Rails.application.routes.url_helpers.send(:"#{ path }_path", *options)
70
+ path = path_method?(path) ? path.to_sym : :"#{ path }_path"
71
+ Rails.application.routes.url_helpers.send(path, *options)
72
+ end
73
+
74
+ # Allow #polymorphic_path to work by delegating missing methods ending
75
+ # with _path or _url to be tried on url_helpers
76
+ #
77
+ def method_missing(method_name, *args, &block)
78
+ if path_method?(method_name)
79
+ Rails.application.routes.url_helpers.try(method_name, *args) || super
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ def path_method?(path)
86
+ path.to_s.match(/_(path|url)\z/)
40
87
  end
41
88
  end
42
89
  end
@@ -6,6 +6,8 @@ module Para
6
6
  included do
7
7
  class_attribute :_class_level_breadcrumbs
8
8
 
9
+ before_action :build_breadcrumbs_manager
10
+
9
11
  helper_method :add_breadcrumb, :breadcrumbs
10
12
  helper ViewHelper
11
13
  end
@@ -15,11 +17,18 @@ module Para
15
17
  end
16
18
 
17
19
  def breadcrumbs
18
- Para.store[:breadcrumbs] ||= begin
19
- manager = Breadcrumbs::Manager.new
20
- _class_level_breadcrumbs.each { |args| manager.add(*args) }
21
- manager
22
- end
20
+ build_breadcrumbs_manager
21
+ end
22
+
23
+ private
24
+
25
+ def build_breadcrumbs_manager
26
+ Para.store['para.breadcrumbs'] ||=
27
+ Breadcrumbs::Manager.new(self).tap do |manager|
28
+ if _class_level_breadcrumbs
29
+ _class_level_breadcrumbs.each { |args| manager.add(*args) }
30
+ end
31
+ end
23
32
  end
24
33
 
25
34
  module ClassMethods
@@ -30,8 +39,12 @@ module Para
30
39
  end
31
40
 
32
41
  module ViewHelper
42
+ # Render the breadcrumbs view depending wether it is in the admin or
43
+ # in front, allowing the front view to be overriden in app
44
+ #
33
45
  def render_breadcrumbs
34
- render partial: 'para/admin/shared/breadcrumbs'
46
+ prefix = controller.admin? ? 'para/admin/' : ''
47
+ render partial: "#{ prefix }shared/breadcrumbs"
35
48
  end
36
49
  end
37
50
  end