├── .gitignore ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── gemfiles └── active_record.gemfile ├── lib ├── panoramic.rb └── panoramic │ ├── orm │ └── active_record.rb │ └── resolver.rb ├── panoramic.gemspec └── spec ├── controllers └── rendering_spec.rb ├── dummy ├── Rakefile ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ └── foo_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── models │ │ ├── .keep │ │ └── database_template.rb │ └── views │ │ └── layouts │ │ └── application.html.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── db │ ├── migrate │ │ └── 20110526132353_create_database_templates.rb │ └── schema.rb └── script │ └── rails ├── orm └── active_record_spec.rb ├── resolver_spec.rb ├── spec_helper.rb └── support ├── describe_private.rb └── factories.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/dummy/db/*.sqlite3 5 | spec/dummy/log/*.log 6 | spec/dummy/tmp/ 7 | .rspec 8 | coverage/* 9 | .rvmrc 10 | gemfiles/*.lock 11 | *.rdb 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5.0 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | gem 'rails-controller-testing' 5 | 6 | group :test do 7 | gem 'rake' 8 | end 9 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Andrea Pavoni - http://andreapavoni.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Panoramic [![Build Status](https://secure.travis-ci.org/apeacox/panoramic.png)](http://travis-ci.org/apeacox/panoramic) 2 | An [ActionView::Resolver] implementation to store rails views (layouts, templates and partials) on database. Simply put: what you can do with views on filesystem, can be done on database. 3 | 4 | **NOTE:** at the moment, only ActiveRecord is supported, I've planned to add more ORMs (see Todo). If you can't wait, adding other ORMs should be very trivial. 5 | 6 | ## Installation 7 | Add the following line to Gemfile: 8 | 9 | ```ruby 10 | gem "panoramic" 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Mandatory fields 16 | Your model should have the following fields: 17 | 18 | * body (text): the source of template 19 | * path (string): where to find template (ex: layouts/application, 20 | you_controller/action, etc...) 21 | * locale (string): it depends from available locales in your app 22 | * handler (string): as locale field, it depends from avaiable handlers 23 | (erb, haml, etc...) 24 | * partial (boolean): determines if it's a partial or not (false by 25 | default) 26 | * format (string): A valid mimetype from Mime::SET.symbols 27 | 28 | they're what the rails' Resolver API needs to lookup templates. 29 | 30 | ### Model 31 | A simple macro in model will activate your new Resolver. You can use a dedicated model to manage all the views in your app, or just for specific needs (ex: you want a custom template for some static pages, the other views will be fetched from filesystem). 32 | 33 | ```ruby 34 | class TemplateStorage < ActiveRecord::Base 35 | store_templates 36 | end 37 | ``` 38 | 39 | ### Controller 40 | To add Panoramic::Resolver in controller, depending on your needs, you may choose: 41 | 42 | * [prepend_view_path]: search for templates *first* in your resolver, *then* on filesystem 43 | * [append_view_path]: search for templates *first* on filesystem, *then* in your resolver 44 | 45 | **NOTE**: the above methods are both class and instance methods. 46 | 47 | 48 | ```ruby 49 | class SomeController < ApplicationController 50 | prepend_view_path TemplateStorage.resolver 51 | 52 | def index 53 | # as you may already know, rails will serve 'some/index' template by default, but it doesn't care where it is stored. 54 | end 55 | 56 | def show 57 | # explicit render 58 | render :template => 'custom_template' 59 | end 60 | 61 | def custom_template 62 | # use another model to fetch templates 63 | prepend_view_path AnotherModel.resolver 64 | end 65 | end 66 | ``` 67 | 68 | And let's say you want to use database template resolving in all your controllers, but 69 | want to use panoramic only for certain paths (prefixed with X) you can use 70 | 71 | ```ruby 72 | class ApplicationController < ActionController::Base 73 | prepend_view_path TemplateStorage.resolver(:only => 'use_this_prefix_only') 74 | end 75 | ``` 76 | 77 | This helps reducing the number of database requests, if Rails for example tries to look 78 | for layouts per controller. 79 | 80 | ### ActionMailer 81 | 82 | ```ruby 83 | class MyEmail < ActionMailer::Base 84 | prepend_view_path TemplateStorage.resolver 85 | ``` 86 | Using prepend_view_path/append_view_path you are stuck to the current context (e.g. the method calling "mail"). 87 | If you want to dynamically change the path depending on a certain variable, call the prepend_view_path/append_view_path inside the method's context with an additional path variable. 88 | This could be useful, if you want to use only one method for sending different templates depending on the template.path . 89 | 90 | ```ruby 91 | class MyEmail < ActionMailer::Base 92 | 93 | def method_that_sets_resolver_path 94 | prepend_view_path TemplateStorage.resolver(:path => model.path) 95 | end 96 | ``` 97 | 98 | ## Documentation 99 | Need more help? Check out ```spec/dummy/```, you'll find a *dummy* rails app I used to make tests ;-) 100 | 101 | ## Testing 102 | Enter Panoramic gem path, run ```bundle install``` to install development and test dependencies, then ```rake spec```. 103 | 104 | 105 | ## Todo 106 | 107 | ### Long term 108 | * add generators 109 | 110 | ## Contributing 111 | Fork, make your changes, then send a pull request. 112 | 113 | ## Credits 114 | The main idea was *heavily inspired* from José Valim's awesome book [Crafting Rails Applications]. It helped me to better understand some Rails internals. 115 | 116 | [ActionView::Resolver]: http://api.rubyonrails.org/classes/ActionView/Resolver.html 117 | [append_view_path]: http://apidock.com/rails/AbstractController/ViewPaths/ClassMethods/append_view_path 118 | [prepend_view_path]: http://apidock.com/rails/AbstractController/ViewPaths/ClassMethods/prepend_view_path 119 | [Crafting Rails Applications]: http://pragprog.com/titles/jvrails/crafting-rails-applications 120 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | begin 4 | require 'bundler' 5 | require 'bundler/setup' 6 | rescue LoadError 7 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 8 | end 9 | 10 | require 'rspec/core' 11 | require 'rspec/core/rake_task' 12 | 13 | Bundler::GemHelper.install_tasks 14 | RSpec::Core::RakeTask.new(:spec) 15 | 16 | ORMS = %w(active_record) 17 | 18 | task :default => "spec:all" 19 | 20 | namespace :spec do 21 | ORMS.each do |orm| 22 | desc "Run Tests against #{orm}" 23 | task orm do 24 | sh "BUNDLE_GEMFILE='gemfiles/#{orm}.gemfile' bundle --quiet" 25 | sh "BUNDLE_GEMFILE='gemfiles/#{orm}.gemfile' bundle exec rake -t spec" 26 | end 27 | end 28 | 29 | desc "Run Tests against all ORMs" 30 | task :all do 31 | ORMS.each { |orm| Rake::Task["spec:#{orm}"].invoke } 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /gemfiles/active_record.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'activerecord', '>= 3.2.8', :require => 'active_record' 4 | 5 | gemspec :path => '../' 6 | -------------------------------------------------------------------------------- /lib/panoramic.rb: -------------------------------------------------------------------------------- 1 | require 'panoramic/resolver' 2 | require 'panoramic/orm/active_record' 3 | -------------------------------------------------------------------------------- /lib/panoramic/orm/active_record.rb: -------------------------------------------------------------------------------- 1 | module Panoramic 2 | module Orm 3 | module ActiveRecord 4 | def store_templates 5 | class_eval do 6 | validates :body, :presence => true 7 | validates :path, :presence => true 8 | validates :format, :inclusion => Mime::SET.symbols.map(&:to_s) 9 | validates :locale, :inclusion => I18n.available_locales.map(&:to_s), :allow_blank => true 10 | validates :handler, :inclusion => ActionView::Template::Handlers.extensions.map(&:to_s) 11 | 12 | after_save { Panoramic::Resolver.instance.clear_cache } 13 | 14 | extend ClassMethods 15 | end 16 | end 17 | 18 | module ClassMethods 19 | def find_model_templates(conditions = {}) 20 | self.where(conditions) 21 | end 22 | 23 | def resolver(options={}) 24 | Panoramic::Resolver.using self, options 25 | end 26 | end 27 | end 28 | end 29 | end 30 | 31 | ActiveRecord::Base.extend Panoramic::Orm::ActiveRecord 32 | -------------------------------------------------------------------------------- /lib/panoramic/resolver.rb: -------------------------------------------------------------------------------- 1 | module Panoramic 2 | class Resolver < ActionView::Resolver 3 | require "singleton" 4 | include Singleton 5 | 6 | # this method is mandatory to implement a Resolver 7 | def find_templates(name, prefix, partial, details, key=nil, locals=[]) 8 | return [] if @@resolver_options[:only] && !@@resolver_options[:only].include?(prefix) 9 | 10 | path = @@resolver_options[:path].present? ? @@resolver_options[:path] : build_path(name, prefix) 11 | 12 | conditions = { 13 | :path => path, 14 | :locale => [normalize_array(details[:locale]).first, nil], 15 | :format => normalize_array(details[:formats]), 16 | :handler => normalize_array(details[:handlers]), 17 | :partial => partial || false 18 | }.merge(details[:additional_criteria].presence || {}) 19 | 20 | @@model.find_model_templates(conditions)&.map do |record| 21 | Rails.logger.debug "Rendering template from database: #{path} (#{record.format})" 22 | initialize_template(record) 23 | end 24 | end 25 | 26 | # Instantiate Resolver by passing a model (decoupled from ORMs) 27 | def self.using(model, options={}) 28 | @@model = model 29 | @@resolver_options = options 30 | self.instance 31 | end 32 | 33 | private 34 | 35 | # Initialize an ActionView::Template object based on the record found. 36 | def initialize_template(record) 37 | source = record.body 38 | identifier = "#{record.class} - #{record.id} - #{record.path.inspect}" 39 | handler = ActionView::Template.registered_template_handler(record.handler) 40 | 41 | ActionView::Template.new(source, identifier, handler, 42 | :locals => [], 43 | :format => Mime[record.format].to_sym, 44 | :virtual_path => virtual_path(record.path, record.partial)) 45 | end 46 | 47 | # Build path with eventual prefix 48 | def build_path(name, prefix) 49 | prefix.present? ? "#{prefix}/#{name}" : name 50 | end 51 | 52 | # Normalize array by converting all symbols to strings. 53 | def normalize_array(array) 54 | array.map(&:to_s) 55 | end 56 | 57 | # returns a path depending if its a partial or template 58 | def virtual_path(path, partial) 59 | return path unless partial 60 | if index = path.rindex("/") 61 | path.insert(index + 1, "_") 62 | else 63 | "_#{path}" 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /panoramic.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "panoramic" 5 | s.version ='0.0.7' 6 | s.platform = Gem::Platform::RUBY 7 | s.authors = ["Andrea Pavoni"] 8 | s.email = ["andrea.pavoni@gmail.com"] 9 | s.homepage = "http://github.com/apeacox/panoramic" 10 | s.summary = %q{Stores rails views on database} 11 | s.description = %q{A gem to store Rails views on database} 12 | s.license = 'MIT' 13 | 14 | s.add_runtime_dependency 'rails', '>= 4.2.0' 15 | s.add_development_dependency 'rails-controller-testing' 16 | s.add_development_dependency 'capybara', '~> 2.5', '>= 2.5.0' 17 | s.add_development_dependency 'factory_bot' 18 | s.add_development_dependency "simplecov" 19 | s.add_development_dependency "sqlite3" 20 | s.add_development_dependency "rspec-rails", '~> 3.0' 21 | 22 | s.files = `git ls-files`.split("\n") 23 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 24 | s.require_paths = ["lib"] 25 | end 26 | -------------------------------------------------------------------------------- /spec/controllers/rendering_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe FooController, type: :controller do 4 | include Capybara::DSL 5 | render_views 6 | 7 | context "renders views fetched from database with" do 8 | it "a basic template" do 9 | FactoryBot.create(:database_template, :path => 'foo/default_layout') 10 | 11 | visit '/foo/default_layout' 12 | 13 | expect(response).to render_template("foo/default_layout" ) 14 | expect(page.body).to match(/something here in the body of the page: 4/) 15 | end 16 | 17 | it "a custom layout" do 18 | FactoryBot.create(:database_template, :path => 'foo/custom_layout') 19 | FactoryBot.create(:database_template, :path => 'layouts/custom', :body => 'This is a layout with body: <%= yield %>') 20 | 21 | visit '/foo/custom_layout' 22 | 23 | expect(response).to render_template("layouts/custom" ) 24 | expect(response).to render_template("foo/custom_layout" ) 25 | expect(page.body).to match(/This is a layout with body:/) 26 | expect(page.body).to match(/something here in the body of the page:/) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/foo_controller.rb: -------------------------------------------------------------------------------- 1 | class FooController < ApplicationController 2 | prepend_view_path DatabaseTemplate.resolver 3 | 4 | layout 'custom', :only => :custom_layout 5 | 6 | def default_layout 7 | end 8 | 9 | def custom_layout 10 | end 11 | end 12 | 13 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreapavoni/panoramic/1e458d30a336e695aa1a34594b6fb0421afd5a6c/spec/dummy/app/models/.keep -------------------------------------------------------------------------------- /spec/dummy/app/models/database_template.rb: -------------------------------------------------------------------------------- 1 | class DatabaseTemplate < ActiveRecord::Base 2 | # include DatabaseTemplates::Orm::ActiveRecord 3 | store_templates 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "action_view/railtie" 8 | require "sprockets/railtie" 9 | # require "rails/test_unit/railtie" 10 | 11 | Bundler.require(*Rails.groups) 12 | require "panoramic" 13 | 14 | module Dummy 15 | class Application < Rails::Application 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration should go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded. 19 | 20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 22 | # config.time_zone = 'Central Time (US & Canada)' 23 | 24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 26 | # config.i18n.default_locale = :de 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Dummy::Application.config.secret_token = 'd3115f72ad5880f2d9996fccbc8fea7778866b94b3653b9e712cf1ae8a21a7d6d3c1e6df116198db542f1be3a9112c9ebc15e43f8eb5138ac7e8a763a25a191c' 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get '/foo/default_layout' => 'foo#default_layout' 3 | get '/foo/custom_layout' => 'foo#custom_layout' 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 54877071639f591ceae3604d3816d0a2e525c2def764ecf760f94f42d055ef968eafaa915fb152caca417edbe1a5019c6692ff1590d69396ff1cbe7e2a676e1e 15 | 16 | test: 17 | secret_key_base: e4e03f1c7167d75bbf4be4ff3353c5cf607ca1d84cd27f7937a5c0d175a201d9c30810f691365f5c9ca20cf92442eb05f3b43dfa2cb38d152148bfefc8dc7b42 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20110526132353_create_database_templates.rb: -------------------------------------------------------------------------------- 1 | class CreateDatabaseTemplates < ActiveRecord::Migration 2 | def self.up 3 | create_table :database_templates do |t| 4 | t.text :body 5 | t.boolean :partial, :default => false 6 | t.string :path 7 | t.string :format 8 | t.string :locale 9 | t.string :handler 10 | 11 | t.timestamps 12 | end 13 | end 14 | 15 | def self.down 16 | drop_table :database_templates 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20110526132353) do 14 | 15 | create_table "database_templates", :force => true do |t| 16 | t.text "body" 17 | t.boolean "partial", :default => false 18 | t.string "path" 19 | t.string "format" 20 | t.string "locale" 21 | t.string "handler" 22 | t.datetime "created_at" 23 | t.datetime "updated_at" 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/orm/active_record_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Panoramic::Orm::ActiveRecord, type: :model do 4 | let(:template) { FactoryBot.build :database_template } 5 | 6 | context "validations" do 7 | it "has body present" do 8 | template.body = nil 9 | expect(template).to_not be_valid 10 | expect(template.errors[:body].size).to eq(1) 11 | end 12 | 13 | context "MIME format" do 14 | it "is valid" do 15 | template.format = 'notknown' 16 | expect(template).to_not be_valid 17 | expect(template.errors[:format].size).to eq(1) 18 | end 19 | 20 | it "is present" do 21 | template.format = nil 22 | expect(template).to_not be_valid 23 | expect(template.errors[:format].size).to eq(1) 24 | end 25 | end 26 | 27 | context "locale" do 28 | it "is valid even if not present" do 29 | template.locale = nil 30 | expect(template).to be_valid 31 | end 32 | end 33 | end 34 | 35 | context "handler" do 36 | it "is valid" do 37 | template.handler = 'notknown' 38 | expect(template).to_not be_valid 39 | expect(template.errors[:handler].size).to eq(1) 40 | end 41 | 42 | it "is present" do 43 | template.handler = nil 44 | expect(template).to_not be_valid 45 | expect(template.errors[:handler].size).to eq(1) 46 | end 47 | end 48 | 49 | context "cache" do 50 | before do 51 | DatabaseTemplate.delete_all 52 | end 53 | 54 | it "is expired on update" do 55 | resolver = DatabaseTemplate.resolver 56 | 57 | cache_key = Object.new 58 | db_template = FactoryBot.create(:database_template, :path => 'foo/some_list', :body => 'Listing something') 59 | 60 | details = { :formats => [:html], :locale => [:en], :handlers => [:erb] } 61 | 62 | template = resolver.find_all("some_list", "foo", false, details, cache_key).first 63 | expect(template.source).to match(/Listing something/) 64 | 65 | db_template.update_attributes(:body => "New body for template") 66 | 67 | template = resolver.find_all("some_list", "foo", false, details, cache_key).first 68 | expect(template.source).to match(/New body for template/) 69 | end 70 | end 71 | 72 | context "#resolver" do 73 | it "#returns a Resolver instance" do 74 | expect(DatabaseTemplate.resolver).to be_a(Panoramic::Resolver) 75 | end 76 | end 77 | 78 | context "ActiveRecord::Base" do 79 | it "responds to store_templates" do 80 | expect(ActiveRecord::Base).to respond_to(:store_templates) 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Panoramic::Resolver do 4 | let(:resolver) { Panoramic::Resolver.using(DatabaseTemplate) } 5 | 6 | context ".find_templates" do 7 | it "should lookup templates for given params" do 8 | template = FactoryBot.create(:database_template, :path => 'foo/example') 9 | details = { :formats => [:html], :locale => [:en], :handlers => [:erb] } 10 | expect(resolver.find_templates('example', 'foo', false, details).first).to_not be_nil 11 | end 12 | 13 | it "should lookup locale agnostic templates for given params" do 14 | template = FactoryBot.create(:database_template, :path => 'foo/example', :locale => nil) 15 | details = { :formats => [:html], :locale => [:en], :handlers => [:erb] } 16 | expect(resolver.find_templates('example', 'foo', false, details).first).to_not be_nil 17 | end 18 | 19 | it "should lookup templates for given params and prefixes" do 20 | resolver = Panoramic::Resolver.using(DatabaseTemplate, :only => 'foo') 21 | details = { :formats => [:html], :locale => [:en], :handlers => [:erb] } 22 | 23 | template = FactoryBot.create(:database_template, :path => 'bar/example') 24 | expect(resolver.find_templates('example', 'bar', false, details).first).to be_nil 25 | 26 | template = FactoryBot.create(:database_template, :path => 'foo/example') 27 | expect(resolver.find_templates('example', 'foo', false, details).first).to_not be_nil 28 | end 29 | 30 | it "should lookup multiple templates" do 31 | resolver = Panoramic::Resolver.using(DatabaseTemplate, :only => 'foo') 32 | details = { :formats => [:html,:text], :locale => [:en], :handlers => [:erb] } 33 | details[:formats].each do |format| 34 | FactoryBot.create(:database_template, :path => 'foo/example', :format => format.to_s) 35 | end 36 | templates = resolver.find_templates('example', 'foo', false, details) 37 | expect(templates.length).to be >= 2 38 | expect(templates.map(&:formats).flatten.uniq).to eq([:html, :text]) 39 | end 40 | end 41 | end 42 | 43 | describe_private Panoramic::Resolver, '(private methods)' do 44 | let(:resolver) { Panoramic::Resolver.using(DatabaseTemplate) } 45 | 46 | context "#build_path" do 47 | it "returns prefix/name if prefix is passed" do 48 | expect(resolver.build_path('path', 'prefix')).to eq('prefix/path') 49 | end 50 | 51 | it "returns name if prefix is nil" do 52 | expect(resolver.build_path('path',nil)).to eq('path') 53 | end 54 | end 55 | 56 | context "#normalize_array" do 57 | it "converts all symbols to strings" do 58 | expect(resolver.normalize_array([:something, 'that', :matters])).to eq(['something','that','matters']) 59 | end 60 | end 61 | 62 | context "#initialize_template" do 63 | let(:template) { FactoryBot.create :database_template } 64 | 65 | it "initializes an ActionView::Template object" do 66 | expect(resolver.initialize_template(template)).to be_a(ActionView::Template) 67 | end 68 | end 69 | 70 | context "#virtual_path" do 71 | it "returns 'path' if is not a partial" do 72 | expect(resolver.virtual_path('path',false)).to eq('path') 73 | end 74 | 75 | it "returns '_path' if is a partial" do 76 | expect(resolver.virtual_path('path',true)).to eq('_path') 77 | end 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | unless ENV['COVERAGE'].nil? 2 | require 'simplecov' 3 | SimpleCov.start 'rails' do 4 | coverage_dir 'coverage' 5 | end 6 | end 7 | 8 | ENV["RAILS_ENV"] = "test" 9 | 10 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 11 | require "rails/test_help" 12 | require "rspec/rails" 13 | require 'factory_bot' 14 | 15 | ActionMailer::Base.delivery_method = :test 16 | ActionMailer::Base.perform_deliveries = true 17 | ActionMailer::Base.default_url_options[:host] = "test.com" 18 | 19 | Rails.backtrace_cleaner.remove_silencers! 20 | 21 | # Configure capybara for integration testing 22 | require "capybara/rails" 23 | Capybara.default_driver = :rack_test 24 | Capybara.default_selector = :css 25 | 26 | # Run any available migration 27 | ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__) 28 | 29 | # Load support files 30 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 31 | 32 | RSpec.configure do |config| 33 | # Remove this line if you don't want RSpec's should and should_not 34 | # methods or matchers 35 | require 'rspec/expectations' 36 | config.include RSpec::Matchers 37 | 38 | # == Mock Framework 39 | config.mock_with :rspec 40 | end 41 | -------------------------------------------------------------------------------- /spec/support/describe_private.rb: -------------------------------------------------------------------------------- 1 | # taken from: http://kailuowang.blogspot.com/2010/08/testing-private-methods-in-rspec.html 2 | def describe_private *args, &block 3 | example = describe *args, &block 4 | klass = args[0] 5 | if klass.is_a? Class 6 | saved_private_instance_methods = klass.private_instance_methods 7 | example.before do 8 | klass.class_eval { public *saved_private_instance_methods } 9 | end 10 | example.after do 11 | klass.class_eval { private *saved_private_instance_methods } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/factories.rb: -------------------------------------------------------------------------------- 1 | # FactoryBot.define :database_template do |t| 2 | FactoryBot.define do 3 | factory :database_template do 4 | path { 'foo/index' } 5 | format { 'html' } 6 | locale { 'en' } 7 | handler { 'erb' } 8 | partial { 'false' } 9 | body { "something here in the body of the page: <%= 2 + 2 %>" } 10 | end 11 | end 12 | --------------------------------------------------------------------------------