para 0.5.4 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/para/admin/async-progress.coffee +29 -0
  3. data/app/assets/javascripts/para/admin/filters-form.coffee +1 -1
  4. data/app/assets/javascripts/para/admin/importers.coffee +38 -0
  5. data/app/assets/javascripts/para/admin/table.coffee +1 -1
  6. data/app/assets/javascripts/para/admin/tabs.coffee +1 -1
  7. data/app/assets/javascripts/para/admin/theme_actions.coffee +1 -1
  8. data/app/assets/javascripts/para/admin/tree.coffee +5 -5
  9. data/app/assets/javascripts/para/admin.coffee +2 -1
  10. data/app/assets/javascripts/para/inputs/nested_many.coffee +1 -1
  11. data/app/assets/javascripts/para/lib/remote-file-forms.coffee +60 -0
  12. data/app/assets/javascripts/para/lib/turbolinks-loading.coffee +1 -1
  13. data/app/assets/javascripts/para/lib/turbolinks-reloader.coffee +19 -0
  14. data/app/assets/stylesheets/para/admin/src/datetimepicker.sass +3 -1
  15. data/app/assets/stylesheets/para/admin/src/fuelux.sass +4 -2
  16. data/app/assets/stylesheets/para/admin/src/nested_many.sass +5 -0
  17. data/app/assets/stylesheets/para/admin/src/redactor.sass +8 -5
  18. data/app/assets/stylesheets/para/admin/src/selectize.sass +7 -4
  19. data/app/assets/stylesheets/para/admin/theme/_breadcrumb.sass +20 -12
  20. data/app/assets/stylesheets/para/admin/theme/_buttons.sass +6 -2
  21. data/app/assets/stylesheets/para/admin/theme/_checkable.sass +4 -2
  22. data/app/assets/stylesheets/para/admin/theme/_commonds.sass +6 -3
  23. data/app/assets/stylesheets/para/admin/theme/_dropdown.sass +3 -1
  24. data/app/assets/stylesheets/para/admin/theme/_form.sass +4 -2
  25. data/app/assets/stylesheets/para/admin/theme/_list.sass +7 -5
  26. data/app/assets/stylesheets/para/admin/theme/_navigation.sass +12 -66
  27. data/app/assets/stylesheets/para/admin/theme/_navtabs.sass +18 -14
  28. data/app/assets/stylesheets/para/admin/theme/_orderable.sass +7 -3
  29. data/app/assets/stylesheets/para/admin/theme/_panel.sass +19 -13
  30. data/app/assets/stylesheets/para/admin/theme/_tree.sass +4 -2
  31. data/app/assets/stylesheets/para/overrides/theme.sass +14 -10
  32. data/app/controllers/para/admin/base_controller.rb +0 -5
  33. data/app/controllers/para/admin/crud_resources_controller.rb +5 -19
  34. data/app/controllers/para/admin/{singleton_resources_controller.rb → form_resources_controller.rb} +4 -4
  35. data/app/controllers/para/admin/imports_controller.rb +68 -0
  36. data/app/controllers/para/admin/resources_controller.rb +0 -1
  37. data/app/decorators/para/component/base_decorator.rb +6 -18
  38. data/app/decorators/para/component/crud_decorator.rb +3 -3
  39. data/app/decorators/para/component/{singleton_resource_decorator.rb → form_decorator.rb} +6 -6
  40. data/app/helpers/para/admin/components_helper.rb +12 -1
  41. data/app/helpers/para/admin/decorators_helper.rb +13 -0
  42. data/app/helpers/para/markup_helper.rb +4 -0
  43. data/app/helpers/para/search_helper.rb +1 -4
  44. data/app/models/para/component/base.rb +17 -14
  45. data/app/models/para/component/crud.rb +17 -0
  46. data/app/models/para/component/form.rb +36 -0
  47. data/app/models/para/component/resource.rb +9 -20
  48. data/app/models/para/component/settings.rb +1 -1
  49. data/app/models/para/library/file.rb +23 -0
  50. data/app/models/para/library.rb +5 -0
  51. data/app/views/para/admin/form_resources/show.html.haml +4 -0
  52. data/app/views/para/admin/imports/_completed.html.haml +16 -0
  53. data/app/views/para/admin/imports/_failed.html.haml +7 -0
  54. data/app/views/para/admin/imports/_progress.html.haml +5 -0
  55. data/app/views/para/admin/imports/new.html.haml +14 -0
  56. data/app/views/para/admin/imports/show.html.haml +10 -0
  57. data/app/views/para/admin/resources/_fields.html.haml +1 -4
  58. data/app/views/para/admin/resources/_imports_menu.html.haml +17 -20
  59. data/app/views/para/admin/resources/_tree.html.haml +1 -1
  60. data/app/views/para/admin/shared/_breadcrumbs.html.haml +1 -4
  61. data/app/views/para/admin/shared/_navigation.html.haml +2 -2
  62. data/app/views/para/form/_tabs.html.haml +1 -1
  63. data/config/locales/en.yml +10 -0
  64. data/config/locales/fr.yml +12 -2
  65. data/db/migrate/20160905134106_create_para_library_files.rb +9 -0
  66. data/lib/generators/para/importer/templates/base_importer.rb +8 -1
  67. data/lib/generators/para/install/install_generator.rb +1 -2
  68. data/lib/generators/para/install/templates/components.rb +9 -5
  69. data/lib/generators/para/tree_item/tree_item_generator.rb +17 -0
  70. data/lib/para/attribute_field/base.rb +12 -4
  71. data/lib/para/attribute_field/enum.rb +7 -3
  72. data/lib/para/component/importable.rb +4 -4
  73. data/lib/para/component.rb +1 -1
  74. data/lib/para/components_cleaner.rb +66 -0
  75. data/lib/para/components_configuration.rb +3 -1
  76. data/lib/para/engine.rb +36 -3
  77. data/lib/para/ext/active_job_status.rb +13 -0
  78. data/lib/para/ext/paperclip.rb +10 -0
  79. data/lib/para/ext/simple_form_extension.rb +6 -0
  80. data/lib/para/ext.rb +1 -0
  81. data/lib/para/form_builder/nested_form.rb +5 -2
  82. data/lib/para/importer/base.rb +84 -7
  83. data/lib/para/inputs/nested_many_input.rb +1 -1
  84. data/lib/para/logging/active_job_log_subscriber.rb +48 -0
  85. data/lib/para/logging.rb +8 -0
  86. data/lib/para/markup/resources_table.rb +2 -4
  87. data/lib/para/model_field_parsers/enums.rb +19 -0
  88. data/lib/para/model_field_parsers/relations.rb +1 -1
  89. data/lib/para/model_field_parsers/store.rb +1 -1
  90. data/lib/para/model_field_parsers.rb +1 -0
  91. data/lib/para/plugins/routes.rb +2 -4
  92. data/lib/para/postgres_extensions_checker.rb +30 -0
  93. data/lib/para/routes.rb +5 -4
  94. data/lib/para/routing/component_controller_constraint.rb +36 -0
  95. data/lib/para/routing.rb +7 -0
  96. data/lib/para/sti/root_model.rb +8 -2
  97. data/lib/para/version.rb +1 -1
  98. data/lib/para.rb +10 -0
  99. data/lib/rails/routing_mapper.rb +59 -27
  100. data/lib/tasks/para_tasks.rake +21 -11
  101. metadata +73 -15
  102. data/app/models/para/component/singleton_resource.rb +0 -35
  103. data/app/views/para/admin/singleton_resources/show.html.haml +0 -4
@@ -6,16 +6,20 @@ module Para
6
6
  def value_for(instance)
7
7
  if (raw_value = instance.send(name)) &&
8
8
  path = enum_path_for(instance, raw_value)
9
- translation = ::I18n.t("activerecord.#{ path }", default: false)
9
+ translation = ::I18n.t("activerecord.#{ path }", default: '')
10
10
 
11
- translation || raw_value
11
+ translation.presence || raw_value
12
12
  end
13
13
  end
14
14
 
15
+ def field_type
16
+ :selectize
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def enum_path_for(instance, key)
18
- ['enums', instance.class.model_name.i18n_key, name, key].join('.')
22
+ ['enums', instance.model_name.i18n_key, name, key].join('.')
19
23
  end
20
24
  end
21
25
  end
@@ -8,14 +8,14 @@ module Para
8
8
  end
9
9
 
10
10
  def importable?
11
- @importable ||= imports.length > 0
11
+ @importable ||= importers.length > 0
12
12
  end
13
13
 
14
14
  # TODO : Move :configuration column store to JSON instead of HStore
15
15
  # which handles more data types and will help us avoid eval here
16
- def imports
17
- @imports ||= if importers.present?
18
- eval(importers)
16
+ def importers
17
+ @importers ||= if (importers = configuration['importers'].presence)
18
+ eval(importers).map(&:constantize)
19
19
  else
20
20
  []
21
21
  end
@@ -25,5 +25,5 @@ require 'para/component/subclassable'
25
25
  require 'para/component/base'
26
26
  require 'para/component/resource'
27
27
  require 'para/component/crud'
28
- require 'para/component/singleton_resource'
28
+ require 'para/component/form'
29
29
  require 'para/component/settings'
@@ -0,0 +1,66 @@
1
+ module Para
2
+ class ComponentsCleaner
3
+ # Hide class instanciation
4
+ def self.run; new.run; end
5
+
6
+ def run
7
+ components.each do |component|
8
+ unless component == Para.components.component_for(component.identifier)
9
+ component.destroy
10
+ end
11
+ end
12
+
13
+ Para::ComponentSection.find_each do |section|
14
+ unless Para.components.section_for(section.identifier)
15
+ section.destroy
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def components
23
+ Para::Component::Base.all.to_a
24
+ rescue ActiveRecord::SubclassNotFound => e
25
+ # When a subclass is not found, define it and try loading all components.
26
+ #
27
+ # Since we're probably about to destroy this class, this sould not
28
+ # produce errors for the end user
29
+ #
30
+ if (match = e.message.match(/the subclass: '([\w:]+)'/))
31
+ define_temporary_component_subclass(match[1])
32
+ return components
33
+ end
34
+
35
+ raise e
36
+ end
37
+
38
+ def define_temporary_component_subclass(subclass_name)
39
+ subclass_path = subclass_name.split('::')
40
+
41
+ subclass_path.each_with_index.each_with_object([]) do |(const_name, index), path|
42
+ parent_name = path.join('::').presence
43
+
44
+ begin
45
+ [parent_name, const_name].compact.join('::').constantize
46
+ rescue NameError
47
+ # Last iterated constnat is the actual component subclass, others must
48
+ # be modules namespacing the component class
49
+ object = if index == (subclass_path.length - 1)
50
+ Class.new(Para::Component::Base)
51
+ else
52
+ Module.new
53
+ end
54
+
55
+ parent = parent_name ? parent_name.constantize : Object
56
+ # Define module or class in parent namespace
57
+ parent.const_set(const_name, object)
58
+ end
59
+
60
+ # Add const name to the path to allow namespaced constants to be
61
+ # defined in next loop iteration
62
+ path << const_name
63
+ end
64
+ end
65
+ end
66
+ end
@@ -86,6 +86,8 @@ module Para
86
86
  end
87
87
 
88
88
  tables_exist
89
+ rescue ActiveRecord::NoDatabaseError
90
+ false # Do not load components when the database is not installed
89
91
  end
90
92
 
91
93
  # Eager loads every file ending with _component.rb that's included in a
@@ -151,7 +153,7 @@ module Para
151
153
 
152
154
  def refresh(attributes = {})
153
155
  self.model = type.where(identifier: identifier).first_or_initialize
154
- model.assign_attributes(attributes.merge(options_with_defaults))
156
+ model.update_with(attributes.merge(options_with_defaults))
155
157
  model.save!
156
158
  end
157
159
 
data/lib/para/engine.rb CHANGED
@@ -29,7 +29,7 @@ module Para
29
29
  end
30
30
 
31
31
  initializer 'Extend paperclip attachment definition' do
32
- return unless Kernel.const_defined?('Paperclip')
32
+ next unless Kernel.const_defined?('Paperclip')
33
33
 
34
34
  ActiveSupport.on_load(:active_record) do
35
35
  ::Paperclip::HasAttachedFile.send(
@@ -39,7 +39,7 @@ module Para
39
39
  end
40
40
 
41
41
  initializer 'Extend cancan ControllerResource class' do
42
- return unless Kernel.const_defined?('CanCan')
42
+ next unless Kernel.const_defined?('CanCan')
43
43
 
44
44
  ActiveSupport.on_load(:active_record) do
45
45
  ::CanCan::ControllerResource.send(
@@ -56,6 +56,14 @@ module Para
56
56
  end
57
57
  end
58
58
 
59
+ initializer 'Extend active job status\' status class' do
60
+ ActiveSupport.on_load(:active_job) do
61
+ ::ActiveJob::Status::Status.send(
62
+ :include, Para::Ext::ActiveJob::StatusMixin
63
+ )
64
+ end
65
+ end
66
+
59
67
  initializer 'Build components tree' do |app|
60
68
  components_config_path = Rails.root.join('config', 'components.rb')
61
69
 
@@ -65,7 +73,32 @@ module Para
65
73
  end
66
74
 
67
75
  initializer 'Load page sections' do |app|
68
- Para::Page::Section.eager_load!
76
+ app.config.to_prepare do
77
+ Para::Page::Section.eager_load!
78
+ end
79
+ end
80
+
81
+ initializer 'Check for extensions installation' do
82
+ Para::PostgresExtensionsChecker.check_all
83
+ end
84
+
85
+ initializer 'Configure ActiveJob' do
86
+ if ActiveSupport::Cache::NullStore === ActiveJob::Status.store
87
+ ActiveJob::Status.store = :memory_store
88
+ end
89
+
90
+ Para::Logging::ActiveJobLogSubscriber.attach_to :active_job
91
+ end
92
+
93
+ # Allow generating resources in the gem without having all the unnecessary
94
+ # files generated
95
+ #
96
+ config.generators do |generators|
97
+ generators.skip_routes true if generators.respond_to?(:skip_routes)
98
+ generators.helper false
99
+ generators.stylesheets false
100
+ generators.javascripts false
101
+ generators.test_framework false
69
102
  end
70
103
  end
71
104
  end
@@ -0,0 +1,13 @@
1
+ module Para
2
+ module Ext
3
+ module ActiveJob
4
+ module StatusMixin
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ delegate :failed?, to: :status_inquiry
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -78,3 +78,13 @@ module Para
78
78
  end
79
79
  end
80
80
  end
81
+
82
+ require 'paperclip/media_type_spoof_detector'
83
+
84
+ module Paperclip
85
+ class MediaTypeSpoofDetector
86
+ def spoofed?
87
+ false
88
+ end
89
+ end
90
+ end
@@ -14,6 +14,12 @@ module Para
14
14
  return name
15
15
  end
16
16
  end
17
+
18
+ if option.respond_to?(:model_name)
19
+ [option.model_name.human, option.id].join(' - ')
20
+ else
21
+ option.to_s
22
+ end
17
23
  end
18
24
  end
19
25
  end
data/lib/para/ext.rb CHANGED
@@ -12,3 +12,4 @@ end
12
12
  require 'para/ext/cancan'
13
13
  require 'para/ext/paperclip'
14
14
  require 'para/ext/simple_form_extension'
15
+ require 'para/ext/active_job_status'
@@ -56,7 +56,7 @@ module Para
56
56
  return false unless nested?
57
57
 
58
58
  reflection = parent_object.class.reflect_on_association(nested_attribute_name)
59
- reflection && (reflection.options[:inverse_of] == field_name)
59
+ reflection && (reflection.options[:inverse_of].to_s == field_name.to_s)
60
60
  end
61
61
 
62
62
  def nested?
@@ -64,7 +64,10 @@ module Para
64
64
  end
65
65
 
66
66
  def nested_attribute_name
67
- options[:nested_attribute_name]
67
+ options[:nested_attribute_name] ||=
68
+ if (match = object_name.match(/\[(\w+)_attributes\]/))
69
+ match[1]
70
+ end
68
71
  end
69
72
 
70
73
  def parent_object
@@ -1,23 +1,100 @@
1
1
  module Para
2
2
  module Importer
3
- class Base
4
- attr_reader :sheet
3
+ class Base < ActiveJob::Base
4
+ include ActiveJob::Status
5
+ # Used to store import errors on the object
6
+ include ActiveModel::Validations
7
+ # Used to translate importer name with rails default `activemodel` i18n keys
8
+ extend ActiveModel::Naming
5
9
 
6
- def initialize(file)
7
- @sheet = Roo::Spreadsheet.open(file.path)
8
- end
10
+ rescue_from Exception, with: :rescue_exception
11
+
12
+ class_attribute :allows_import_errors
13
+
14
+ attr_reader :file, :sheet
15
+
16
+ def perform(file, options = {})
17
+ @file = file
18
+ @sheet = Roo::Spreadsheet.open(file.attachment_path, options)
19
+ progress.total = sheet.last_row - 1
9
20
 
10
- def run
11
21
  ActiveRecord::Base.transaction do
12
22
  (2..(sheet.last_row)).each do |index|
13
- import_from_row(sheet.row(index))
23
+ begin
24
+ progress.increment
25
+ import_from_row(sheet.row(index))
26
+ rescue ActiveRecord::RecordInvalid => error
27
+ if allows_import_errors?
28
+ add_errors_from(index, error.record)
29
+ else
30
+ raise
31
+ end
32
+ end
14
33
  end
15
34
  end
35
+
36
+ status.update(errors: errors.full_messages)
16
37
  end
17
38
 
39
+ private
40
+
18
41
  def import_from_row(row)
19
42
  raise '#import_from_row(row) must be defined'
20
43
  end
44
+
45
+ def add_errors_from(index, record)
46
+ # The file's row number starts at 1 and headers are striped, so we
47
+ # add 2 to the index to obtain the row number
48
+ row_name = I18n.t('para.import.row_error_prefix', number: index)
49
+
50
+ record.errors.messages.each do |attribute, messages|
51
+ messages.each do |message|
52
+ full_message = record.errors.full_message(attribute, message)
53
+
54
+ if (value = record.send(attribute)).presence
55
+ full_message << ' (<b>' << value << '</b>)'
56
+ end
57
+
58
+ errors.add(row_name, full_message)
59
+ end
60
+ end
61
+ end
62
+
63
+ def allows_import_errors?
64
+ !!self.class.allows_import_errors
65
+ end
66
+
67
+ def self.allow_import_errors!
68
+ self.allows_import_errors = true
69
+ end
70
+
71
+ def headers
72
+ @headers ||= sheet.row(1)
73
+ end
74
+
75
+ def rescue_exception(exception)
76
+ status.update(status: :failed)
77
+
78
+ tag_logger(self.class.name, self.job_id) do
79
+ ActiveSupport::Notifications.instrument "failed.active_job",
80
+ adapter: self.class.queue_adapter, job: self, exception: exception
81
+ end
82
+
83
+ if defined?(ExceptionNotifier)
84
+ ExceptionNotifier.notify_exception(
85
+ exception, data: {
86
+ importer: self.class.name,
87
+ payload: {
88
+ file_type: file.class,
89
+ file_id: file.id,
90
+ file_name: file.attachment_file_name
91
+ }
92
+ }
93
+ )
94
+ end
95
+
96
+ raise exception
97
+ end
21
98
  end
22
99
  end
23
100
  end
@@ -11,7 +11,7 @@ module Para
11
11
  # Load existing resources
12
12
  resources = object.send(attribute_name)
13
13
  # Order them if the list should be orderable
14
- resources = resources.order(:position) if orderable
14
+ resources = resources.sort_by(&:position) if orderable
15
15
 
16
16
  locals = options.fetch(:locals, {})
17
17
 
@@ -0,0 +1,48 @@
1
+ module Para
2
+ module Logging
3
+ class ActiveJobLogSubscriber < ActiveSupport::LogSubscriber
4
+ def failed(event)
5
+ fatal do
6
+ job = event.payload[:job]
7
+ buffer = []
8
+ buffer << "#{ job.class.name } (Job ID: #{ job.job_id }) failed" + args_info(job)
9
+ buffer << ''
10
+ buffer << [event.payload[:exception].class, event.payload[:exception].message].join(' - ')
11
+ buffer << ''
12
+ buffer << '-------------------'
13
+ buffer << ''
14
+ buffer += event.payload[:exception].backtrace
15
+ buffer << ''
16
+ buffer.compact.join("\n")
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def args_info(job)
23
+ if job.arguments.any?
24
+ ' with arguments: ' +
25
+ job.arguments.map { |arg| format(arg).inspect }.join(', ')
26
+ end
27
+ end
28
+
29
+ def format(arg)
30
+ case arg
31
+ when Hash
32
+ arg.transform_values { |value| format(value) }
33
+ when Array
34
+ arg.map { |value| format(value) }
35
+ when GlobalID::Identification
36
+ arg.to_global_id rescue arg
37
+ else
38
+ arg
39
+ end
40
+ end
41
+
42
+ def logger
43
+ ActiveJob::Base.logger
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,8 @@
1
+ module Para
2
+ module Logging
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ActiveJobLogSubscriber
6
+ end
7
+ end
8
+
@@ -79,8 +79,6 @@ module Para
79
79
  field_name = nil
80
80
  end
81
81
 
82
- sort = options.delete(:sort) || field_name
83
-
84
82
  label = if Symbol === field_name
85
83
  model.human_attribute_name(field_name)
86
84
  elsif block
@@ -90,7 +88,7 @@ module Para
90
88
  end
91
89
 
92
90
  content_tag(:th, options) do
93
- if sort != field_name
91
+ if (sort = options.delete(:sort))
94
92
  view.sort_link(search, *sort, label, hide_indicator: true)
95
93
  elsif searchable?(field_name)
96
94
  view.sort_link(search, field_name, label, hide_indicator: true)
@@ -155,7 +153,7 @@ module Para
155
153
  }
156
154
 
157
155
  view.link_to(path, options) do
158
- content_tag(:i, '', class: 'fa fa-refresh')
156
+ content_tag(:i, '', class: 'fa fa-copy')
159
157
  end
160
158
  end
161
159
 
@@ -0,0 +1,19 @@
1
+ module Para
2
+ module ModelFieldParsers
3
+ class Enums < Para::ModelFieldParsers::Base
4
+ register :enums, self
5
+
6
+ def parse!
7
+ model.defined_enums.each do |key, _|
8
+ fields_hash[key] = AttributeField::EnumField.new(
9
+ model, name: key, type: 'enum'
10
+ )
11
+ end
12
+ end
13
+
14
+ def applicable?
15
+ model.respond_to?(:defined_enums) && !model.defined_enums.empty?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -38,7 +38,7 @@ module Para
38
38
  fields_hash[name] = AttributeField::HasManyField.new(
39
39
  model, name: name, type: 'has_many', field_type: 'selectize'
40
40
  )
41
- else
41
+ elsif !reflection.options[:through]
42
42
  fields_hash[name] = AttributeField::BelongsToField.new(
43
43
  model, name: name, type: 'belongs_to', field_type: 'selectize'
44
44
  )
@@ -14,7 +14,7 @@ module Para
14
14
 
15
15
  field_names.each do |field_name|
16
16
  fields_hash[field_name] = AttributeField::Base.new(
17
- model, name: field_name, type: :string
17
+ model, name: field_name, type: :string, searchable: false
18
18
  )
19
19
  end
20
20
  end
@@ -21,3 +21,4 @@ require 'para/model_field_parsers/relations'
21
21
  require 'para/model_field_parsers/redactor'
22
22
  require 'para/model_field_parsers/globalize'
23
23
  require 'para/model_field_parsers/store'
24
+ require 'para/model_field_parsers/enums'
@@ -13,10 +13,8 @@ module Para
13
13
  router = self.router
14
14
 
15
15
  router.instance_eval do
16
- namespace :admin do
17
- scope module: [:para, identifier].join('/').to_sym, as: identifier do
18
- router.instance_eval(&block)
19
- end
16
+ scope module: [:para, identifier].join('/').to_sym do
17
+ router.instance_eval(&block)
20
18
  end
21
19
  end
22
20
  end
@@ -0,0 +1,30 @@
1
+ module Para
2
+ class PostgresExtensionsChecker
3
+ class << self
4
+ def check_all
5
+ %w(hstore unaccent).each do |extname|
6
+ unless extension_exists?(extname)
7
+ # Could not use Rails.logger here, using puts as a temporary
8
+ # solution.
9
+ puts "[Warning] PostgreSQL \"#{ extname }\" extension is not " +
10
+ "installed in your database. This means that you " +
11
+ "missing some migrations that you can install " +
12
+ "with the following command : " +
13
+ "`rake para_engine:install:migrations` and then migrate."
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def extension_exists?(extname)
21
+ ActiveRecord::Base.connection.execute(
22
+ "SELECT COUNT(*) FROM pg_catalog.pg_extension " +
23
+ "WHERE extname = '#{ extname }'"
24
+ ).first['count'].to_i > 0
25
+ rescue ActiveRecord::NoDatabaseError
26
+ true # Do not issue warning when no database is installed
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/para/routes.rb CHANGED
@@ -14,11 +14,12 @@ module Para
14
14
  scope module: :para do
15
15
  namespace :admin do
16
16
  get '/' => 'main#index'
17
-
18
- crud_component :crud, scope: ':model'
19
- singleton_resource_component :singleton, scope: ':model'
20
- component :settings
21
17
  end
18
+
19
+ # Components are namespaced into :admin in their respective methods
20
+ crud_component
21
+ form_component
22
+ component :settings
22
23
  end
23
24
 
24
25
  block.call if block
@@ -0,0 +1,36 @@
1
+ # Allows constraining routing to components that explicitly declares to use a
2
+ # given controller to manage their resources.
3
+ #
4
+ # It's mainly used to allow users to override the default controller for the
5
+ # resources of a given Crud or Form components without having to
6
+ # subclass the component and declare all the routes again
7
+ #
8
+ module Para
9
+ module Routing
10
+ class ComponentControllerConstraint
11
+ attr_reader :controller
12
+
13
+ def initialize(controller)
14
+ @controller = controller.to_sym
15
+ end
16
+
17
+ def matches?(request)
18
+ component = component_for(request.params[:component_id])
19
+ return false unless component
20
+ component.controller.to_sym == controller
21
+ end
22
+
23
+ private
24
+
25
+ def component_for(component_slug)
26
+ self.class.components[component_slug] ||= Para::Component::Base.find_by_slug(component_slug)
27
+ end
28
+
29
+ # Request based components cache
30
+ #
31
+ def self.components
32
+ RequestStore.store['para.components_by_id'] ||= {}
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module Para
2
+ module Routing
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ComponentControllerConstraint
6
+ end
7
+ end
@@ -16,8 +16,8 @@ module Para
16
16
 
17
17
  module ClassMethods
18
18
  def eager_load!
19
- return if @eager_loaded
20
- @eager_loaded = true
19
+ return if RequestStore.store[descendants_eager_load_key]
20
+ RequestStore.store[descendants_eager_load_key] = true
21
21
 
22
22
  models_dir = Rails.root.join('app', 'models')
23
23
 
@@ -38,6 +38,12 @@ module Para
38
38
 
39
39
  private
40
40
 
41
+ def descendants_eager_load_key
42
+ @descendants_eager_load_key ||= [
43
+ 'para', 'root_model', 'descendants_eager_loaded', name
44
+ ].join(':')
45
+ end
46
+
41
47
  # Allows the including class to define `.subclasses_namespace` class
42
48
  # method to override the namespace and directory used to eager load the
43
49
  # subclasses.
data/lib/para/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Para
2
- VERSION = "0.5.4"
2
+ VERSION = '0.6.2'
3
3
  end