forest_cms 0.98.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +204 -0
- data/Rakefile +37 -0
- data/app/assets/config/forest_manifest.js +2 -0
- data/app/assets/fonts/FontAwesome.otf +0 -0
- data/app/assets/fonts/fontawesome-webfont.eot +0 -0
- data/app/assets/fonts/fontawesome-webfont.svg +2671 -0
- data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/app/assets/fonts/fontawesome-webfont.woff +0 -0
- data/app/assets/fonts/fontawesome-webfont.woff2 +0 -0
- data/app/assets/images/forest/favicons/android-chrome-192x192.png +0 -0
- data/app/assets/images/forest/favicons/android-chrome-512x512.png +0 -0
- data/app/assets/images/forest/favicons/apple-touch-icon.png +0 -0
- data/app/assets/images/forest/favicons/browserconfig.xml +9 -0
- data/app/assets/images/forest/favicons/favicon-16x16.png +0 -0
- data/app/assets/images/forest/favicons/favicon-32x32.png +0 -0
- data/app/assets/images/forest/favicons/favicon.ico +0 -0
- data/app/assets/images/forest/favicons/manifest.json.erb +18 -0
- data/app/assets/images/forest/favicons/mstile-150x150.png +0 -0
- data/app/assets/images/forest/favicons/safari-pinned-tab.svg +21 -0
- data/app/assets/images/forest/forest-low.jpg +0 -0
- data/app/assets/images/forest/forest.jpg +0 -0
- data/app/assets/javascripts/forest/admin/lib/jquery_plugins.js +32 -0
- data/app/assets/javascripts/forest/admin/main.js +59 -0
- data/app/assets/javascripts/forest/admin/partials/add_block.js +22 -0
- data/app/assets/javascripts/forest/admin/partials/block_collapser.js +49 -0
- data/app/assets/javascripts/forest/admin/partials/block_errors.js +11 -0
- data/app/assets/javascripts/forest/admin/partials/block_selector_inputs.js +61 -0
- data/app/assets/javascripts/forest/admin/partials/cocoon_events.js +3 -0
- data/app/assets/javascripts/forest/admin/partials/cocoon_sortable_field_sets.js +55 -0
- data/app/assets/javascripts/forest/admin/partials/collage_inputs.js +286 -0
- data/app/assets/javascripts/forest/admin/partials/color_inputs.js +24 -0
- data/app/assets/javascripts/forest/admin/partials/datepicker_init.js +70 -0
- data/app/assets/javascripts/forest/admin/partials/documentation_sidebar_affix.js +22 -0
- data/app/assets/javascripts/forest/admin/partials/dropdown_block_selector.js +106 -0
- data/app/assets/javascripts/forest/admin/partials/edit_header.js +22 -0
- data/app/assets/javascripts/forest/admin/partials/file_upload_init.js +111 -0
- data/app/assets/javascripts/forest/admin/partials/forest_tables.js +42 -0
- data/app/assets/javascripts/forest/admin/partials/infinite_loader.js +89 -0
- data/app/assets/javascripts/forest/admin/partials/markdown_text_editor_init.js +89 -0
- data/app/assets/javascripts/forest/admin/partials/media_item_chooser_modal.js.erb +259 -0
- data/app/assets/javascripts/forest/admin/partials/menus.js +79 -0
- data/app/assets/javascripts/forest/admin/partials/multi_checkbox_selector.js +27 -0
- data/app/assets/javascripts/forest/admin/partials/persisted_value_warnings.js +38 -0
- data/app/assets/javascripts/forest/admin/partials/repeater_inputs.js +24 -0
- data/app/assets/javascripts/forest/admin/partials/select2_init.js +165 -0
- data/app/assets/javascripts/forest/admin/partials/select_tag_form_submitter.js +40 -0
- data/app/assets/javascripts/forest/admin/partials/sortable_gallery.js +45 -0
- data/app/assets/javascripts/forest/admin/partials/sortable_page_block_position_setter.js +37 -0
- data/app/assets/javascripts/forest/admin/partials/status_scheduler.js +16 -0
- data/app/assets/javascripts/forest/admin/partials/tab_templates.js +93 -0
- data/app/assets/javascripts/forest/admin/partials/tables.js +7 -0
- data/app/assets/javascripts/forest/admin/partials/tooltips.js +7 -0
- data/app/assets/javascripts/forest/admin/partials/turboform_form_submitter.js +15 -0
- data/app/assets/javascripts/forest/admin/vendor/imagesloaded.pkgd.min.js +7 -0
- data/app/assets/javascripts/forest/admin/vendor/jquery-ui-timepicker-addon.js +2291 -0
- data/app/assets/javascripts/forest/admin/vendor/jquery.ba-throttle-debounce.min.js +9 -0
- data/app/assets/javascripts/forest/admin/vendor/jquery.nestable.js +756 -0
- data/app/assets/javascripts/forest/admin/vendor/select2.full.min.js +3 -0
- data/app/assets/javascripts/forest/admin/vendor/simplemde.min.js +15 -0
- data/app/assets/javascripts/forest/application_admin.js +29 -0
- data/app/assets/javascripts/forest/application_admin_from_host_app.js +1 -0
- data/app/assets/javascripts/forest/application_public.js +19 -0
- data/app/assets/javascripts/forest/cable.js +13 -0
- data/app/assets/javascripts/forest/lib/jquery-3.3.1.min.js +2 -0
- data/app/assets/javascripts/forest/main.js +59 -0
- data/app/assets/javascripts/forest/public/partials/analytics.js +16 -0
- data/app/assets/stylesheets/forest/admin/_bootstrap-variables.scss +877 -0
- data/app/assets/stylesheets/forest/admin/_variables.scss +13 -0
- data/app/assets/stylesheets/forest/admin/base/_foundation.scss +17 -0
- data/app/assets/stylesheets/forest/admin/mixins/_breakpoints.scss +26 -0
- data/app/assets/stylesheets/forest/admin/mixins/_glyphicons.scss +278 -0
- data/app/assets/stylesheets/forest/admin/mixins/_layout.scss +13 -0
- data/app/assets/stylesheets/forest/admin/mixins/_links.scss +10 -0
- data/app/assets/stylesheets/forest/admin/mixins/_lists.scss +10 -0
- data/app/assets/stylesheets/forest/admin/mixins/_mixins.scss +6 -0
- data/app/assets/stylesheets/forest/admin/mixins/_triangles.scss +21 -0
- data/app/assets/stylesheets/forest/admin/partials/_admin.scss +57 -0
- data/app/assets/stylesheets/forest/admin/partials/_affix.scss +5 -0
- data/app/assets/stylesheets/forest/admin/partials/_block_errors.scss +15 -0
- data/app/assets/stylesheets/forest/admin/partials/_block_layout.scss +48 -0
- data/app/assets/stylesheets/forest/admin/partials/_block_type_edit_ui.scss +15 -0
- data/app/assets/stylesheets/forest/admin/partials/_bootstrap_customizations.scss +12 -0
- data/app/assets/stylesheets/forest/admin/partials/_collage_inputs.scss +122 -0
- data/app/assets/stylesheets/forest/admin/partials/_documentation.scss +7 -0
- data/app/assets/stylesheets/forest/admin/partials/_dropdown_block_selector.scss +19 -0
- data/app/assets/stylesheets/forest/admin/partials/_edit_header.scss +16 -0
- data/app/assets/stylesheets/forest/admin/partials/_file_upload.scss +3 -0
- data/app/assets/stylesheets/forest/admin/partials/_forest_tables.scss +20 -0
- data/app/assets/stylesheets/forest/admin/partials/_forms_and_inputs.scss +72 -0
- data/app/assets/stylesheets/forest/admin/partials/_helper_classes.scss +24 -0
- data/app/assets/stylesheets/forest/admin/partials/_infinite_loader.scss +8 -0
- data/app/assets/stylesheets/forest/admin/partials/_markdown_editor.scss +12 -0
- data/app/assets/stylesheets/forest/admin/partials/_media_item_chooser.scss +168 -0
- data/app/assets/stylesheets/forest/admin/partials/_media_library.scss +109 -0
- data/app/assets/stylesheets/forest/admin/partials/_pages.scss +6 -0
- data/app/assets/stylesheets/forest/admin/partials/_pagination.scss +5 -0
- data/app/assets/stylesheets/forest/admin/partials/_repeater_inputs.scss +57 -0
- data/app/assets/stylesheets/forest/admin/partials/_resizable_ui.scss +1 -0
- data/app/assets/stylesheets/forest/admin/partials/_select2_custom.scss +20 -0
- data/app/assets/stylesheets/forest/admin/partials/_sessions.scss +13 -0
- data/app/assets/stylesheets/forest/admin/partials/_slug_attributes.scss +9 -0
- data/app/assets/stylesheets/forest/admin/partials/_sortable_field_sets.scss +22 -0
- data/app/assets/stylesheets/forest/admin/partials/_sortable_select2.scss +18 -0
- data/app/assets/stylesheets/forest/admin/partials/_sortable_ui.scss +15 -0
- data/app/assets/stylesheets/forest/admin/partials/_status_indicators.scss +33 -0
- data/app/assets/stylesheets/forest/admin/partials/_status_scheduler.scss +9 -0
- data/app/assets/stylesheets/forest/admin/partials/_tooltips.scss +5 -0
- data/app/assets/stylesheets/forest/admin/partials/_turbolinks.scss +5 -0
- data/app/assets/stylesheets/forest/admin/partials/_typography.scss +19 -0
- data/app/assets/stylesheets/forest/admin/partials/_ui.scss +32 -0
- data/app/assets/stylesheets/forest/admin/sprockets/font-awesome.scss +2337 -0
- data/app/assets/stylesheets/forest/admin/sprockets/main_bootstrap.scss +3 -0
- data/app/assets/stylesheets/forest/admin/vendor/_jquery-ui-timepicker-addon.scss +30 -0
- data/app/assets/stylesheets/forest/admin/vendor/_nestable.scss +347 -0
- data/app/assets/stylesheets/forest/admin/vendor/_select2_1_core.scss +484 -0
- data/app/assets/stylesheets/forest/admin/vendor/_select2_2_bootstrap.scss +612 -0
- data/app/assets/stylesheets/forest/admin/vendor/_simplemde.min.scss +7 -0
- data/app/assets/stylesheets/forest/admin_overrides.scss +1 -0
- data/app/assets/stylesheets/forest/application_admin.css +23 -0
- data/app/assets/stylesheets/forest/application_public.css +16 -0
- data/app/assets/stylesheets/forest/main_admin.scss +16 -0
- data/app/assets/stylesheets/forest/main_public.scss +4 -0
- data/app/assets/stylesheets/forest/public/base/_admin_error_messages.scss +15 -0
- data/app/assets/stylesheets/forest/public/base/_foundation.scss +24 -0
- data/app/assets/stylesheets/forest/public/base/_images.scss +14 -0
- data/app/assets/stylesheets/forest/scaffolds.scss +87 -0
- data/app/assets/stylesheets/jquery.fileupload.scss +37 -0
- data/app/classes/forest/block_set.rb +26 -0
- data/app/classes/forest/error.rb +15 -0
- data/app/classes/forest/markdown_renderer.rb +31 -0
- data/app/classes/search_index_manager.rb +83 -0
- data/app/controllers/admin/block_kinds_controller.rb +31 -0
- data/app/controllers/admin/block_layouts_controller.rb +47 -0
- data/app/controllers/admin/cache_purge_controller.rb +11 -0
- data/app/controllers/admin/dashboard_controller.rb +6 -0
- data/app/controllers/admin/documentation_controller.rb +5 -0
- data/app/controllers/admin/errors_controller.rb +9 -0
- data/app/controllers/admin/forest_controller.rb +96 -0
- data/app/controllers/admin/imports_controller.rb +34 -0
- data/app/controllers/admin/media_items_controller.rb +107 -0
- data/app/controllers/admin/menus_controller.rb +69 -0
- data/app/controllers/admin/pages_controller.rb +105 -0
- data/app/controllers/admin/settings_controller.rb +66 -0
- data/app/controllers/admin/translations_controller.rb +58 -0
- data/app/controllers/admin/user_groups_controller.rb +69 -0
- data/app/controllers/admin/users_controller.rb +88 -0
- data/app/controllers/application_controller.rb +2 -0
- data/app/controllers/errors_controller.rb +26 -0
- data/app/controllers/forest_controller.rb +39 -0
- data/app/controllers/media_items_controller.rb +12 -0
- data/app/controllers/pages_controller.rb +39 -0
- data/app/controllers/public_controller.rb +5 -0
- data/app/helpers/admin_helper.rb +95 -0
- data/app/helpers/admin_info_helper.rb +12 -0
- data/app/helpers/admin_settings_helper.rb +16 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/block_helper.rb +25 -0
- data/app/helpers/cache_helper.rb +23 -0
- data/app/helpers/forest_errors_helper.rb +34 -0
- data/app/helpers/form_helper.rb +93 -0
- data/app/helpers/image_helper.rb +65 -0
- data/app/helpers/markdown_helper.rb +25 -0
- data/app/helpers/meta_helper.rb +238 -0
- data/app/helpers/navigation_helper.rb +18 -0
- data/app/helpers/status_indicator_helper.rb +7 -0
- data/app/inputs/block_selector_input.rb +16 -0
- data/app/inputs/collage_input.rb +63 -0
- data/app/inputs/color_input.rb +30 -0
- data/app/inputs/currency_input.rb +22 -0
- data/app/inputs/datepicker_input.rb +19 -0
- data/app/inputs/gallery_input.rb +67 -0
- data/app/inputs/grouped_collection_select_input.rb +19 -0
- data/app/inputs/image_input.rb +82 -0
- data/app/inputs/repeater_input.rb +58 -0
- data/app/jobs/forest/application_job.rb +4 -0
- data/app/mailers/forest/application_mailer.rb +6 -0
- data/app/models/application_record.rb +13 -0
- data/app/models/block_kind.rb +54 -0
- data/app/models/block_layout.rb +19 -0
- data/app/models/block_slot.rb +49 -0
- data/app/models/blocks/base_block.rb +134 -0
- data/app/models/cache_record.rb +2 -0
- data/app/models/concerns/attachable.rb +126 -0
- data/app/models/concerns/blockable.rb +65 -0
- data/app/models/concerns/filter_controller_scopes.rb +13 -0
- data/app/models/concerns/forest/cache_buster.rb +112 -0
- data/app/models/concerns/searchable.rb +200 -0
- data/app/models/concerns/sluggable.rb +70 -0
- data/app/models/concerns/statusable.rb +71 -0
- data/app/models/concerns/versionable.rb +13 -0
- data/app/models/forest/application_record.rb +111 -0
- data/app/models/import.rb +43 -0
- data/app/models/media_item.rb +136 -0
- data/app/models/menu.rb +65 -0
- data/app/models/page.rb +154 -0
- data/app/models/setting.rb +142 -0
- data/app/models/translation.rb +97 -0
- data/app/models/user.rb +45 -0
- data/app/models/user_group.rb +17 -0
- data/app/policies/application_policy.rb +71 -0
- data/app/policies/block_kind_policy.rb +2 -0
- data/app/policies/block_layout_policy.rb +2 -0
- data/app/policies/block_record_policy.rb +13 -0
- data/app/policies/dashboard_policy.rb +19 -0
- data/app/policies/media_item_policy.rb +5 -0
- data/app/policies/menu_policy.rb +2 -0
- data/app/policies/page_policy.rb +9 -0
- data/app/policies/setting_policy.rb +2 -0
- data/app/policies/translation_policy.rb +2 -0
- data/app/policies/user_group_policy.rb +2 -0
- data/app/policies/user_policy.rb +17 -0
- data/app/views/admin/_file_upload.html.erb +75 -0
- data/app/views/admin/_flash_notifications.html.erb +20 -0
- data/app/views/admin/_navigation.html.erb +61 -0
- data/app/views/admin/api/index/_header.json.jbuilder +2 -0
- data/app/views/admin/api/index/_records.json.jbuilder +6 -0
- data/app/views/admin/block_kinds/_form.html.erb +34 -0
- data/app/views/admin/block_kinds/edit.html.erb +3 -0
- data/app/views/admin/block_kinds/index.html.erb +31 -0
- data/app/views/admin/block_layouts/_form.html.erb +33 -0
- data/app/views/admin/block_layouts/edit.html.erb +3 -0
- data/app/views/admin/block_layouts/index.html.erb +29 -0
- data/app/views/admin/block_layouts/new.html.erb +3 -0
- data/app/views/admin/block_layouts/show.html.erb +24 -0
- data/app/views/admin/block_record/_block_layout.html.erb +67 -0
- data/app/views/admin/block_record/_block_slot_field_buttons.html.erb +15 -0
- data/app/views/admin/block_record/_block_slot_fields.html.erb +37 -0
- data/app/views/admin/block_record/_block_slot_fields_template.html.erb +50 -0
- data/app/views/admin/dashboard/_panel.html.erb +30 -0
- data/app/views/admin/dashboard/index.html.erb +18 -0
- data/app/views/admin/documentation/_body.html.erb +9 -0
- data/app/views/admin/documentation/_sidebar.html.erb +17 -0
- data/app/views/admin/documentation/index.html.erb +87 -0
- data/app/views/admin/form_inputs/_slug.html.erb +11 -0
- data/app/views/admin/form_inputs/_status.html.erb +20 -0
- data/app/views/admin/form_inputs/collage/_media_item_fields.html.erb +41 -0
- data/app/views/admin/headers/_edit.html.erb +68 -0
- data/app/views/admin/headers/_index.html.erb +61 -0
- data/app/views/admin/headers/_new.html.erb +14 -0
- data/app/views/admin/headers/_show.html.erb +37 -0
- data/app/views/admin/imports/edit.html.erb +60 -0
- data/app/views/admin/media_items/_form.html.erb +54 -0
- data/app/views/admin/media_items/_media_item_grid_layout.html.erb +23 -0
- data/app/views/admin/media_items/_media_item_list_layout.html.erb +32 -0
- data/app/views/admin/media_items/_modal.html.erb +62 -0
- data/app/views/admin/media_items/edit.html.erb +3 -0
- data/app/views/admin/media_items/index.html.erb +58 -0
- data/app/views/admin/media_items/index.json.jbuilder +2 -0
- data/app/views/admin/media_items/new.html.erb +3 -0
- data/app/views/admin/menus/_form.html.erb +92 -0
- data/app/views/admin/menus/_list_item.html.erb +39 -0
- data/app/views/admin/menus/edit.html.erb +3 -0
- data/app/views/admin/menus/index.html.erb +29 -0
- data/app/views/admin/menus/new.html.erb +3 -0
- data/app/views/admin/menus/show.html.erb +3 -0
- data/app/views/admin/navigation/_resources_dropdown.html.erb +12 -0
- data/app/views/admin/pages/_additional_inputs.html.erb +1 -0
- data/app/views/admin/pages/_form.html.erb +52 -0
- data/app/views/admin/pages/_table_row.html.erb +14 -0
- data/app/views/admin/pages/edit.html.erb +3 -0
- data/app/views/admin/pages/index.html.erb +33 -0
- data/app/views/admin/pages/index.json.jbuilder +2 -0
- data/app/views/admin/pages/new.html.erb +3 -0
- data/app/views/admin/pages/show.html.erb +3 -0
- data/app/views/admin/settings/_form.html.erb +25 -0
- data/app/views/admin/settings/edit.html.erb +3 -0
- data/app/views/admin/settings/index.html.erb +38 -0
- data/app/views/admin/settings/new.html.erb +3 -0
- data/app/views/admin/settings/show.html.erb +19 -0
- data/app/views/admin/settings/types/_boolean.html.erb +1 -0
- data/app/views/admin/settings/types/_image.html.erb +10 -0
- data/app/views/admin/settings/types/_integer.html.erb +5 -0
- data/app/views/admin/settings/types/_string.html.erb +5 -0
- data/app/views/admin/settings/types/_text.html.erb +5 -0
- data/app/views/admin/shared/_admin_head_additions.html.erb +1 -0
- data/app/views/admin/shared/navigation/_navbar.html.erb +8 -0
- data/app/views/admin/translations/_form.html.erb +31 -0
- data/app/views/admin/translations/edit.html.erb +3 -0
- data/app/views/admin/translations/index.html.erb +41 -0
- data/app/views/admin/translations/new.html.erb +3 -0
- data/app/views/admin/translations/show.html.erb +19 -0
- data/app/views/admin/user_groups/_form.html.erb +11 -0
- data/app/views/admin/user_groups/edit.html.erb +3 -0
- data/app/views/admin/user_groups/index.html.erb +29 -0
- data/app/views/admin/user_groups/new.html.erb +3 -0
- data/app/views/admin/user_groups/show.html.erb +9 -0
- data/app/views/admin/users/_form.html.erb +41 -0
- data/app/views/admin/users/edit.html.erb +3 -0
- data/app/views/admin/users/index.html.erb +35 -0
- data/app/views/admin/users/new.html.erb +3 -0
- data/app/views/admin/users/show.html.erb +4 -0
- data/app/views/blocks/_show.html.erb +21 -0
- data/app/views/devise/confirmations/new.html.erb +28 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +31 -0
- data/app/views/devise/passwords/new.html.erb +27 -0
- data/app/views/devise/registrations/edit.html.erb +39 -0
- data/app/views/devise/registrations/new.html.erb +1 -0
- data/app/views/devise/sessions/_default_login.html.erb +25 -0
- data/app/views/devise/sessions/_first_time_login.html.erb +22 -0
- data/app/views/devise/sessions/new.html.erb +12 -0
- data/app/views/devise/shared/_flash_notifications.html.erb +3 -0
- data/app/views/devise/shared/_links.html.erb +25 -0
- data/app/views/devise/unlocks/new.html.erb +28 -0
- data/app/views/errors/internal_server_error.html.erb +2 -0
- data/app/views/errors/internal_server_error.json.jbuilder +4 -0
- data/app/views/errors/not_found.html.erb +2 -0
- data/app/views/errors/not_found.json.jbuilder +4 -0
- data/app/views/errors/unprocessable.html.erb +2 -0
- data/app/views/errors/unprocessable.json.jbuilder +4 -0
- data/app/views/forest/_analytics.html.erb +10 -0
- data/app/views/forest/_favicons.html.erb +7 -0
- data/app/views/kaminari/_first_page.html.erb +11 -0
- data/app/views/kaminari/_gap.html.erb +8 -0
- data/app/views/kaminari/_last_page.html.erb +11 -0
- data/app/views/kaminari/_next_page.html.erb +11 -0
- data/app/views/kaminari/_page.html.erb +12 -0
- data/app/views/kaminari/_paginator.html.erb +25 -0
- data/app/views/kaminari/_prev_page.html.erb +11 -0
- data/app/views/layouts/_shared_head.html.erb +48 -0
- data/app/views/layouts/admin.html.erb +19 -0
- data/app/views/layouts/application.html.erb +15 -0
- data/app/views/layouts/devise.html.erb +12 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/media_items/show.html.erb +29 -0
- data/app/views/pages/show.html.erb +1 -0
- data/app/views/public/index.html.erb +2 -0
- data/app/views/shared/_footer.html.erb +9 -0
- data/app/views/shared/_header.html.erb +9 -0
- data/app/views/shared/_header_colophon.html.erb +7 -0
- data/app/views/shared/_menu.html.erb +5 -0
- data/app/views/shared/_menu_item.html.erb +30 -0
- data/app/views/shared/_subnav.html.erb +7 -0
- data/config/initializers/aws.rb +17 -0
- data/config/initializers/devise.rb +275 -0
- data/config/initializers/forest.rb +5 -0
- data/config/initializers/paperclip.rb +1 -0
- data/config/initializers/rails_extensions.rb +6 -0
- data/config/initializers/simple_form.rb +164 -0
- data/config/initializers/simple_form/markdown.rb +21 -0
- data/config/initializers/simple_form/remote.rb +38 -0
- data/config/initializers/simple_form/sortable.rb +28 -0
- data/config/initializers/simple_form_bootstrap.rb +155 -0
- data/config/locales/devise.en.yml +62 -0
- data/config/locales/en.yml +24 -0
- data/config/locales/simple_form.en.yml +31 -0
- data/config/routes.rb +61 -0
- data/db/migrate/20161217191000_devise_create_users.rb +48 -0
- data/db/migrate/20161217200300_create_pages.rb +21 -0
- data/db/migrate/20161217205353_create_menus.rb +12 -0
- data/db/migrate/20161219033747_create_settings.rb +14 -0
- data/db/migrate/20161219231741_create_media_items.rb +18 -0
- data/db/migrate/20161225212203_create_user_groups.rb +10 -0
- data/db/migrate/20161225212258_create_join_table_user_groups_users.rb +8 -0
- data/db/migrate/20170109175932_create_block_kinds.rb +14 -0
- data/db/migrate/20170109180818_create_block_slots.rb +17 -0
- data/db/migrate/20170109185414_create_base_blocks.rb +8 -0
- data/db/migrate/20170818025124_create_block_layouts.rb +13 -0
- data/db/migrate/20170818025229_add_block_layout_to_block_slots.rb +5 -0
- data/db/migrate/20170818025500_remove_layout_from_block_slots.rb +5 -0
- data/db/migrate/20170821161755_add_unique_index_to_block_layouts.rb +6 -0
- data/db/migrate/20170826173346_create_translations.rb +12 -0
- data/db/migrate/20170910204934_index_attachment_content_type_on_media_items.rb +5 -0
- data/db/migrate/20180206035546_create_cache_records.rb +14 -0
- data/db/migrate/20180415034427_add_redirect_to_pages.rb +5 -0
- data/db/migrate/20180618190248_add_index_created_at_to_media_items.rb +5 -0
- data/db/migrate/20180625172226_add_blockable_metadata_to_pages.rb +6 -0
- data/db/migrate/20180626182949_remove_attachable_from_media_items.rb +7 -0
- data/db/migrate/20180712162414_add_media_item_status_to_media_items.rb +6 -0
- data/db/migrate/20190223155013_add_type_to_block_kinds.rb +6 -0
- data/db/migrate/20190423173909_add_seo_title_to_pages.rb +5 -0
- data/lib/forest.rb +9 -0
- data/lib/forest/engine.rb +51 -0
- data/lib/forest/migrations.rb +86 -0
- data/lib/forest/rails/active_record/has_many_ordered.rb +30 -0
- data/lib/forest/rails/active_record/has_repeatable.rb +19 -0
- data/lib/forest/rails/core_ext/enumerable.rb +17 -0
- data/lib/forest/version.rb +3 -0
- data/lib/generators/forest/block/USAGE +8 -0
- data/lib/generators/forest/block/block_generator.rb +103 -0
- data/lib/generators/forest/block/templates/_block.html.erb +2 -0
- data/lib/generators/forest/block/templates/_block.scss.erb +5 -0
- data/lib/generators/forest/block/templates/_block_edit_fields.html.erb +21 -0
- data/lib/generators/forest/block/templates/create_table_migration.rb +31 -0
- data/lib/generators/forest/block/templates/model.rb +41 -0
- data/lib/generators/forest/install/USAGE +2 -0
- data/lib/generators/forest/install/install_generator.rb +72 -0
- data/lib/generators/forest/scaffold/USAGE +8 -0
- data/lib/generators/forest/scaffold/scaffold_generator.rb +133 -0
- data/lib/generators/forest/scaffold/templates/admin_controller.rb +67 -0
- data/lib/generators/forest/scaffold/templates/controller.rb +20 -0
- data/lib/generators/forest/scaffold/templates/create_table_migration.rb +35 -0
- data/lib/generators/forest/scaffold/templates/model.rb +21 -0
- data/lib/generators/forest/scaffold/templates/policy.rb +2 -0
- data/lib/generators/forest/scaffold/templates/views/admin/_form.html.erb +56 -0
- data/lib/generators/forest/scaffold/templates/views/admin/edit.html.erb +3 -0
- data/lib/generators/forest/scaffold/templates/views/admin/index.html.erb +58 -0
- data/lib/generators/forest/scaffold/templates/views/admin/index.json.jbuilder +2 -0
- data/lib/generators/forest/scaffold/templates/views/admin/new.html.erb +3 -0
- data/lib/generators/forest/scaffold/templates/views/admin/show.html.erb +12 -0
- data/lib/generators/forest/scaffold/templates/views/public/index.html.erb +1 -0
- data/lib/generators/forest/scaffold/templates/views/public/show.html.erb +17 -0
- data/lib/tasks/forest_db_tasks.rake +176 -0
- data/lib/tasks/forest_elasticsearch_tasks.rake +22 -0
- metadata +719 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class BlockLayout < Forest::ApplicationRecord
|
|
2
|
+
include Sluggable
|
|
3
|
+
|
|
4
|
+
has_many :block_slots, inverse_of: :block_layout
|
|
5
|
+
|
|
6
|
+
validates_presence_of :display_name
|
|
7
|
+
|
|
8
|
+
def self.resource_description
|
|
9
|
+
"A block layout is a grouping of blocks that may appear in a particular part of the page. For example, you may have block layouts that apply to the sidebar or header areas of a page."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.default_layout
|
|
13
|
+
BlockLayout.find_by_slug('default')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def slug_attribute
|
|
17
|
+
display_name
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
class BlockSlot < Forest::ApplicationRecord
|
|
2
|
+
parent_class = self
|
|
3
|
+
|
|
4
|
+
validates_presence_of :block_kind
|
|
5
|
+
validates :block_record, presence: true, on: :update
|
|
6
|
+
|
|
7
|
+
belongs_to :block, polymorphic: true, dependent: :destroy
|
|
8
|
+
belongs_to :block_record, polymorphic: true #, optional: true
|
|
9
|
+
belongs_to :block_kind, inverse_of: :block_slots
|
|
10
|
+
belongs_to :block_layout, inverse_of: :block_slots
|
|
11
|
+
|
|
12
|
+
before_validation :create_block_if_block_attributes_empty
|
|
13
|
+
|
|
14
|
+
accepts_nested_attributes_for :block, reject_if: :all_blank, allow_destroy: true
|
|
15
|
+
|
|
16
|
+
scope :by_layout, -> (block_layout) { where(block_layout_id: block_layout.id) }
|
|
17
|
+
|
|
18
|
+
# Merge these blockable params into the blockable record's strong params
|
|
19
|
+
def self.blockable_params
|
|
20
|
+
{
|
|
21
|
+
block_slots_attributes: [
|
|
22
|
+
:id, :_destroy, :position,
|
|
23
|
+
:block_type, :block_record_type,
|
|
24
|
+
:block_id, :block_kind_id, :block_layout_id, :block_record_id,
|
|
25
|
+
block_attributes: [*BlockKind.block_kind_params]
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def block_attributes=(attributes)
|
|
31
|
+
if BlockKind.where(name: self.block_kind.name).exists?
|
|
32
|
+
self.block ||= self.block_kind.name.constantize.new
|
|
33
|
+
self.block.assign_attributes(attributes)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_block_if_block_attributes_empty
|
|
38
|
+
if BlockKind.where(name: self.block_kind.name).exists? && BlockKind.where(name: self.block_kind.name).first.block.permitted_params.blank?
|
|
39
|
+
self.block ||= self.block_kind.name.constantize.new
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def build_block(kind = nil)
|
|
46
|
+
kind ||= self.block_kind.name.constantize
|
|
47
|
+
self.block = kind.new
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
class BaseBlock < Forest::ApplicationRecord
|
|
2
|
+
self.abstract_class = true
|
|
3
|
+
|
|
4
|
+
has_one :block_slot,
|
|
5
|
+
as: :block,
|
|
6
|
+
inverse_of: :block,
|
|
7
|
+
class_name: 'BlockSlot',
|
|
8
|
+
dependent: :destroy
|
|
9
|
+
|
|
10
|
+
# Override to customize the display name of the block.
|
|
11
|
+
def self.display_name
|
|
12
|
+
'Base Block'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Override to add a Bootstrap 3 glyphicon next to the block's title.
|
|
16
|
+
def self.display_icon
|
|
17
|
+
'glyphicon glyphicon-tree-conifer'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.kind
|
|
21
|
+
BlockKind.find_by_name(name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def display_name
|
|
25
|
+
self.class.display_name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def hidden?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def block?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Override to add css classes to a block's wrapper.
|
|
37
|
+
def css_class
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Override to add css styles to a block's wrapper.
|
|
41
|
+
def css_styles
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Override to add data attributes to a block's wrapper. Make sure this is a hash.
|
|
45
|
+
def data_attributes
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def display_icon
|
|
49
|
+
self.class.display_icon
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def permitted_params
|
|
53
|
+
self.class.permitted_params
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def block_id
|
|
57
|
+
"#{self.model_name.singular.dasherize}-#{self.id}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def block_kind
|
|
61
|
+
@block_kind ||= BlockKind.find_by_name(self.class.name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def block_record
|
|
65
|
+
@block_record ||= block_slot.block_record
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def block_record_id
|
|
69
|
+
block_record.block_record.id
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def block_layout
|
|
73
|
+
block_slot.block_layout
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def blocks
|
|
77
|
+
block_record.blocks(block_layout: block_layout)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def index
|
|
81
|
+
blocks.index(self)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def first?
|
|
85
|
+
index == 0
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def last?
|
|
89
|
+
index == blocks.length - 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def first_of_kind?
|
|
93
|
+
blocks.first_of_kind(self.class) == self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def last_of_kind?
|
|
97
|
+
blocks.last_of_kind(self.class) == self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def even_of_kind?
|
|
101
|
+
blocks.kind(self.class).index(self).even?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def odd_of_kind?
|
|
105
|
+
blocks.kind(self.class).index(self).odd?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def previous
|
|
109
|
+
return nil if first?
|
|
110
|
+
blocks[index - 1]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def next
|
|
114
|
+
return nil if last?
|
|
115
|
+
blocks[index + 1]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def non_indexed_attributes
|
|
119
|
+
%W(id created_at updated_at)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Override this in your block to define which attributes should be indexed for search
|
|
123
|
+
def indexed_attributes
|
|
124
|
+
self.class.columns.select { |c| %i(text string).include?(c.type) }
|
|
125
|
+
.collect(&:name)
|
|
126
|
+
.reject { |c| c =~ /_url$/ } - non_indexed_attributes
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def as_indexed_json(options={})
|
|
130
|
+
self.as_json({
|
|
131
|
+
only: indexed_attributes
|
|
132
|
+
})
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Attachable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
has_attached_file :attachment,
|
|
6
|
+
styles: {
|
|
7
|
+
thumb: '200x200#',
|
|
8
|
+
small: '600x600>',
|
|
9
|
+
medium: '1200x1200>',
|
|
10
|
+
large: '2000x2000>'
|
|
11
|
+
},
|
|
12
|
+
convert_options: {
|
|
13
|
+
thumb: lambda { |r| r.gif? ? '' : '-unsharp 0x1' },
|
|
14
|
+
small: lambda { |r| r.gif? ? '' : '-unsharp 0x1' },
|
|
15
|
+
medium: lambda { |r| r.gif? ? '' : '-unsharp 1.5x1+0.7+0.02' },
|
|
16
|
+
large: lambda { |r| r.gif? ? '' : '-unsharp 1.5x1+0.7+0.02' },
|
|
17
|
+
all: lambda { |r| r.gif? ? '-strip -auto-orient -colorspace sRGB' : '-strip -auto-orient -quality 85 -colorspace sRGB -interlace Plane -sampling-factor 4:2:0' }
|
|
18
|
+
},
|
|
19
|
+
default_url: '/images/:style/missing.png'
|
|
20
|
+
|
|
21
|
+
do_not_validate_attachment_file_type :attachment
|
|
22
|
+
|
|
23
|
+
before_post_process :skip_for_non_images
|
|
24
|
+
before_post_process :rename_attachment
|
|
25
|
+
after_post_process :extract_dimensions
|
|
26
|
+
after_post_process :collect_garbage
|
|
27
|
+
|
|
28
|
+
serialize :dimensions
|
|
29
|
+
|
|
30
|
+
belongs_to :attachable, polymorphic: true, optional: true
|
|
31
|
+
|
|
32
|
+
scope :by_content_type, -> (content_type) { where(attachment_content_type: content_type) }
|
|
33
|
+
scope :images, -> { where('attachment_content_type LIKE ?', '%image%') }
|
|
34
|
+
scope :videos, -> { where('attachment_content_type LIKE ?', '%video%') }
|
|
35
|
+
scope :pdfs, -> { where('attachment_content_type LIKE ?', '%pdf%') }
|
|
36
|
+
|
|
37
|
+
def large_attachment_url
|
|
38
|
+
attachment.url(:large)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def image?
|
|
42
|
+
(attachment_content_type =~ %r{^(image|(x-)?application)/(bmp|gif|jpeg|jpg|pjpeg|png|x-png)$}).present?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def video?
|
|
46
|
+
(attachment_content_type =~ %r{^video\/}).present?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def file?
|
|
50
|
+
!image?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def gif?
|
|
54
|
+
attachment_content_type == 'image/gif'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def display_content_type
|
|
58
|
+
if image?
|
|
59
|
+
'image'
|
|
60
|
+
elsif video?
|
|
61
|
+
'video'
|
|
62
|
+
elsif file?
|
|
63
|
+
'file'
|
|
64
|
+
else
|
|
65
|
+
'media item'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def dimensions_at(size)
|
|
70
|
+
geometry = attachment.options[:styles][size].split('x')
|
|
71
|
+
w = geometry.first.to_f
|
|
72
|
+
ratio = dimensions[:width] / w
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
width: dimensions[:width] / ratio,
|
|
76
|
+
height: dimensions[:height] / ratio
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def file_extension
|
|
81
|
+
File.extname(attachment_file_name).downcase
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def display_file_name
|
|
85
|
+
attachment_file_name.sub(/(--\d*)?#{Regexp.quote(file_extension)}$/i, '')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def set_default_metadata
|
|
91
|
+
if self.title.blank?
|
|
92
|
+
self.title = display_file_name
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Append a timestamp to the end of the file name to avoid cache issues with a CDN, e.g. CloudFront.
|
|
97
|
+
def rename_attachment
|
|
98
|
+
self.attachment.instance_write(:file_name, "#{display_file_name}--#{Time.now.to_i}#{file_extension}")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def skip_for_non_images
|
|
102
|
+
image?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Retrieves dimensions for image assets
|
|
106
|
+
# @note Do this after resize operations to account for auto-orientation.
|
|
107
|
+
# https://github.com/thoughtbot/paperclip/wiki/Extracting-image-dimensions
|
|
108
|
+
def extract_dimensions
|
|
109
|
+
return unless image?
|
|
110
|
+
tempfile = attachment.queued_for_write[:original]
|
|
111
|
+
unless tempfile.nil?
|
|
112
|
+
geometry = Paperclip::Geometry.from_file(tempfile)
|
|
113
|
+
self.dimensions = {
|
|
114
|
+
width: geometry.width.to_i,
|
|
115
|
+
height: geometry.height.to_i
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Not sure if this is a good idea, but Heroku's memory gets bogged down immediately
|
|
121
|
+
# when using paperclip and imagemagick.
|
|
122
|
+
def collect_garbage
|
|
123
|
+
GC.start
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Blockable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
parent_class = self
|
|
6
|
+
|
|
7
|
+
before_save :touch_self
|
|
8
|
+
after_save :set_blockable_metadata
|
|
9
|
+
|
|
10
|
+
has_many :block_slots, -> { order(:position) },
|
|
11
|
+
as: :block_record,
|
|
12
|
+
inverse_of: :block_record,
|
|
13
|
+
foreign_key: 'block_record_id',
|
|
14
|
+
dependent: :destroy
|
|
15
|
+
|
|
16
|
+
accepts_nested_attributes_for :block_slots, allow_destroy: true
|
|
17
|
+
|
|
18
|
+
def self.blockable?
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def blockable?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def blocks(options = {})
|
|
27
|
+
block_layout = options.fetch(:block_layout, nil)
|
|
28
|
+
block_layout_name = block_layout.try(:slug).try(:underscore).presence || 'default'
|
|
29
|
+
block_type = options.fetch(:kind, nil)
|
|
30
|
+
|
|
31
|
+
instance_variable_get("@#{block_layout_name}_blocks") || instance_variable_set("@#{block_layout_name}_blocks", begin
|
|
32
|
+
if block_type
|
|
33
|
+
b = block_slots.includes(:block).where(block_kind_id: block_type.kind.id)
|
|
34
|
+
else
|
|
35
|
+
b = block_slots.includes(:block, :block_layout)
|
|
36
|
+
end
|
|
37
|
+
if block_layout.present?
|
|
38
|
+
b = b.select { |bs| bs.block_layout == block_layout }
|
|
39
|
+
end
|
|
40
|
+
Forest::BlockSet.new b.collect(&:block)
|
|
41
|
+
end)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def featured_image_from_blocks
|
|
45
|
+
blocks.find { |b| b.try(:media_item).present? }.try(:media_item)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def touch_self
|
|
51
|
+
if self.respond_to?(:updated_at)
|
|
52
|
+
self.updated_at = Time.now
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Blockable metadata is a way to cache and store data on the blockable record itself to avoid expensive database lookups
|
|
57
|
+
# when querying information about a record that may come from the reord's blocks.
|
|
58
|
+
def set_blockable_metadata
|
|
59
|
+
if self.respond_to?(:blockable_metadata)
|
|
60
|
+
self.blockable_metadata['featured_image_url'] = featured_image_from_blocks.try(:attachment).try(:url, :large)
|
|
61
|
+
self.update_column(:blockable_metadata, self.blockable_metadata)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module FilterControllerScopes
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
has_scope :by_id
|
|
6
|
+
has_scope :by_title
|
|
7
|
+
has_scope :by_slug
|
|
8
|
+
has_scope :by_created_at
|
|
9
|
+
has_scope :by_updated_at
|
|
10
|
+
has_scope :by_status
|
|
11
|
+
has_scope :fuzzy_search
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Include the CacheBuster concern in a model to automatically update
|
|
2
|
+
# all of a model's associated records (hopefully).
|
|
3
|
+
#
|
|
4
|
+
# There are only two hard things in Computer Science: cache invalidation and naming things.
|
|
5
|
+
# -- Phil Karlton
|
|
6
|
+
|
|
7
|
+
module Forest::CacheBuster
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
after_save :touch_associations
|
|
12
|
+
after_save :touch_dependent_records
|
|
13
|
+
after_destroy :touch_associations
|
|
14
|
+
after_destroy :touch_dependent_records
|
|
15
|
+
|
|
16
|
+
# A cached set of associations is stored in a polymorphic record after every save.
|
|
17
|
+
# This allows us to know what the previous set of associated records was during a
|
|
18
|
+
# record's after_save callback, which is important when associations are removed.
|
|
19
|
+
has_one :cache_record, as: :cacheable, dependent: :destroy
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def touch_associations
|
|
24
|
+
# Find all relevant associations
|
|
25
|
+
has_many_associations = self.class.reflect_on_all_associations(:has_many)
|
|
26
|
+
habtm_many_associations = self.class.reflect_on_all_associations(:has_and_belongs_to_many)
|
|
27
|
+
all_associations = has_many_associations + habtm_many_associations
|
|
28
|
+
|
|
29
|
+
# Collect a set of associated records (ActiveRecord::Associations objects)
|
|
30
|
+
association_groups = all_associations.collect { |association| self.send(association.plural_name) }
|
|
31
|
+
|
|
32
|
+
to_update = {}
|
|
33
|
+
time_now = Time.now
|
|
34
|
+
|
|
35
|
+
# Find the cache_record or build a new one
|
|
36
|
+
cache_record = cache_record.presence || self.build_cache_record
|
|
37
|
+
|
|
38
|
+
# Extract the associated class name and ids from the cache_record
|
|
39
|
+
if cache_record.data.present?
|
|
40
|
+
cache_record.data.each do |cache_record_item|
|
|
41
|
+
klass = cache_record_item['klass']
|
|
42
|
+
id = cache_record_item['id']
|
|
43
|
+
to_update[klass] ||= []
|
|
44
|
+
to_update[klass] << id
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Update the associations currently attached to the record
|
|
49
|
+
association_groups.each do |records|
|
|
50
|
+
if records.try :any?
|
|
51
|
+
if records.class.parent.name == 'ActiveRecord::Associations'
|
|
52
|
+
if records.column_names.include?('updated_at')
|
|
53
|
+
logger.debug { "[Forest] CacheBuster is updating associations for #{records.length} #{records.first.model_name.plural}" }
|
|
54
|
+
records.unscope(:order).update_all updated_at: time_now
|
|
55
|
+
# Expire model cache_key
|
|
56
|
+
records.klass.name.safe_constantize&.try(:expire_cache_key)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Delete record ids from the to_update hash if we update them here
|
|
60
|
+
records.to_a.flatten.each do |record|
|
|
61
|
+
to_update[record.class.name] ||= []
|
|
62
|
+
to_update[record.class.name].delete(record.id)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check all associations and see if they are blocks. If they are, collect the unique associated
|
|
69
|
+
# block records and touch each one.
|
|
70
|
+
associated_blocks = association_groups.flatten.select { |r| r.try(:block?) }
|
|
71
|
+
if associated_blocks.present?
|
|
72
|
+
associated_blocks.collect(&:block_record).uniq.each do |block_record|
|
|
73
|
+
logger.debug { "[Forest] CacheBuster is updating block_record #{block_record.class.to_s} that was associated with a block." }
|
|
74
|
+
block_record.touch
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Update any remaining associations
|
|
79
|
+
to_update.each do |class_name, ids|
|
|
80
|
+
klass = class_name.safe_constantize
|
|
81
|
+
next unless klass.present?
|
|
82
|
+
records = klass.where(id: ids)
|
|
83
|
+
if records.present?
|
|
84
|
+
if records.column_names.include?('updated_at')
|
|
85
|
+
logger.debug { "[Forest] CacheBuster is updating remaining associations for #{records.length} #{records.first.model_name.plural} inferred from the cache_record" }
|
|
86
|
+
records.unscope(:order).update_all(updated_at: time_now)
|
|
87
|
+
# Expire model cache_key
|
|
88
|
+
klass.try(:expire_cache_key)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Store the new set of associations in the cache_record
|
|
94
|
+
cache_record.data = association_groups.flatten.collect do |record|
|
|
95
|
+
{ klass: record.class.name, id: record.id }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Save the cache_record
|
|
99
|
+
cache_record.save
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# In some situations a block might rely on data that isn't associated via an ActiveRecord
|
|
103
|
+
# Association. For example, a CurrentExhibitions block depends on Exhibition, but doesn't rely
|
|
104
|
+
# on an association. In that case, override this method in the model and bust the associated data
|
|
105
|
+
# as necessary, using something like the commented out code here.
|
|
106
|
+
def touch_dependent_records
|
|
107
|
+
# For example:
|
|
108
|
+
# logger.debug { "[Forest] CacheBuster is touching dependent records for #{'CurrentExhibitionsBlock'.safe_constantize.length} CurrentExhibitionsBlocks" }
|
|
109
|
+
# 'CurrentExhibitionsBlock'.safe_constantize&.update_all(updated_at: Time.now)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|