├── .gitignore ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── bin ├── benchmark_server ├── console └── setup ├── lib ├── turbo_partial.rb └── turbo_partial │ ├── railtie.rb │ ├── renderer.rb │ ├── renderer_rails5.rb │ └── template_cache.rb ├── test ├── dummy_app │ ├── app │ │ └── views │ │ │ ├── admin │ │ │ └── beers │ │ │ │ └── index.html.erb │ │ │ ├── beers │ │ │ ├── _beer.html.erb │ │ │ ├── _beer2.html.erb │ │ │ └── index.html.erb │ │ │ ├── bench │ │ │ ├── _partial.html.erb │ │ │ └── show.html.erb │ │ │ └── sushis │ │ │ ├── _sushi.html.erb │ │ │ └── index.html.erb │ ├── config.ru │ ├── dummy_app.rb │ └── tmp │ │ └── development_secret.txt ├── integration │ ├── render_absolute_test.rb │ └── render_relative_test.rb └── test_helper.rb └── turbo_partial.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | tmp/ 9 | Gemfile.lock 10 | .byebug_history 11 | *.log 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.6 5 | before_install: gem install bundler -v 1.16.1 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in turbo_partial.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Akira Matsuda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TurboPartial 2 | 3 | Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/turbo_partial`. To experiment with that code, run `bin/console` for an interactive prompt. 4 | 5 | TODO: Delete this and the text above, and describe your gem 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'turbo_partial' 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install turbo_partial 22 | 23 | ## Usage 24 | 25 | TODO: Write usage instructions here 26 | 27 | ## Development 28 | 29 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 30 | 31 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 32 | 33 | ## Contributing 34 | 35 | Bug reports and pull requests are welcome on GitHub at https://github.com/amatsuda/turbo_partial. 36 | 37 | ## License 38 | 39 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/*_test.rb"] 10 | end 11 | 12 | task :default => :test 13 | -------------------------------------------------------------------------------- /bin/benchmark_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'rails' 5 | require 'turbo_partial' 6 | require 'action_view' 7 | require 'action_view/railtie' 8 | require 'action_view/base' 9 | 10 | Bundler.require 11 | 12 | ENV['RAILS_ENV'] = 'production' 13 | 14 | require_relative '../test/dummy_app/dummy_app' 15 | APP_PATH = Rails.root.join('dummy_app').to_s 16 | 17 | require "rails/command" 18 | 19 | puts "Starting up the benchmark server. Visit http://localhost:3000/ for benchmarking." 20 | puts 21 | 22 | Rails::Command.invoke 'server' 23 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "turbo_partial" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/turbo_partial.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'turbo_partial/railtie' 4 | -------------------------------------------------------------------------------- /lib/turbo_partial/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboPartial 4 | class Railtie < ::Rails::Railtie 5 | initializer 'turbo_partial' do 6 | ActiveSupport.on_load :action_view do 7 | if ActionView::VERSION::MAJOR >= 6 8 | require_relative 'renderer' 9 | ActionView::Renderer.prepend TurboPartial::Renderer 10 | else 11 | require_relative 'renderer_rails5' 12 | ActionView::Base.prepend TurboPartial::Renderer::Rails5 13 | ActionView::Template.prepend TurboPartial::Template::CurrentTemplateSetter 14 | end 15 | 16 | require_relative 'template_cache' 17 | ActionView::Template.prepend TurboPartial::Template::TemplateCacheAppender 18 | ActionView::LookupContext::DetailsKey.singleton_class.prepend TurboPartial::LookupContext::TemplateCacheClearer 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/turbo_partial/renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboPartial 4 | module Renderer 5 | def render_partial(context, options, &block) 6 | partial = options[:partial] 7 | 8 | # render relative (./) 9 | if partial.start_with? './' 10 | current_template = context.instance_variable_get :@current_template 11 | current_path = current_template.identifier 12 | current_ext = current_path[/\..*/] 13 | partial_path = File.join(File.dirname(current_path), "_#{partial[2..-1]}#{current_ext}") 14 | 15 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 16 | partial_template.render context, options[:locals] 17 | else 18 | options[:partial] = partial[2..-1] 19 | super 20 | end 21 | 22 | # render relative (../) 23 | elsif partial.start_with? '../' 24 | current_template = context.instance_variable_get :@current_template 25 | current_path = current_template.identifier 26 | current_ext = current_path[/\..*/] 27 | absolute_partial_path = File.expand_path partial, File.dirname(current_path) 28 | partial_path = "#{absolute_partial_path.sub(/\/([^\/]*)$/, '/_\1')}#{current_ext}" 29 | 30 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 31 | partial_template.render context, options[:locals] 32 | else 33 | current_view_path = context.view_paths.paths.detect {|vp| current_path.start_with? vp.path}&.path 34 | options[:partial] = absolute_partial_path.sub "#{current_view_path}/", '' 35 | super 36 | end 37 | 38 | # render absolute 39 | elsif partial.start_with? '/' 40 | current_template = context.instance_variable_get :@current_template 41 | current_path = current_template.identifier 42 | current_view_path = context.view_paths.paths.detect {|vp| current_path.start_with? vp.path}&.path 43 | current_ext = current_path[/\..*/] 44 | partial_path = "#{current_view_path}#{partial.sub(/\/([^\/]*)$/, '/_\1')}#{current_ext}" 45 | 46 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 47 | partial_template.render context, options[:locals] 48 | else 49 | options[:partial] = partial[1..-1] 50 | super 51 | end 52 | 53 | else 54 | super 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/turbo_partial/renderer_rails5.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboPartial 4 | module Template 5 | module CurrentTemplateSetter 6 | def render(view, *) 7 | view.current_template, current_template_was = self, view.current_template 8 | super 9 | ensure 10 | view.current_template = current_template_was 11 | end 12 | end 13 | end 14 | 15 | module Renderer 16 | module Rails5 17 | attr_accessor :current_template 18 | 19 | def render(options = {}, locals = {}, &block) 20 | case options 21 | when String 22 | if options.start_with?('./') || options.start_with?('../') || options.start_with?('/') 23 | render({partial: options, locals: locals}, locals, &block) 24 | else 25 | super 26 | end 27 | when Hash 28 | if (partial = options[:partial]) 29 | # render relative (./) 30 | if partial.start_with? './' 31 | current_path = @current_template.identifier 32 | current_ext = current_path[/\..*/] 33 | partial_path = File.join(File.dirname(current_path), "_#{partial[2..-1]}#{current_ext}") 34 | 35 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 36 | partial_template.render self, options[:locals] 37 | else 38 | options[:partial] = partial[2..-1] 39 | super 40 | end 41 | 42 | # render relative (../) 43 | elsif partial.start_with? '../' 44 | current_path = @current_template.identifier 45 | current_ext = current_path[/\..*/] 46 | absolute_partial_path = File.expand_path partial, File.dirname(current_path) 47 | partial_path = "#{absolute_partial_path.sub(/\/([^\/]*)$/, '/_\1')}#{current_ext}" 48 | 49 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 50 | partial_template.render self, options[:locals] 51 | else 52 | current_view_path = view_paths.paths.detect {|vp| current_path.start_with? vp.instance_variable_get(:@path)}&.instance_variable_get(:@path) 53 | options[:partial] = absolute_partial_path.sub "#{current_view_path}/", '' 54 | super 55 | end 56 | 57 | # render absolute 58 | elsif partial.start_with? '/' 59 | current_path = @current_template.identifier 60 | current_view_path = view_paths.paths.detect {|vp| current_path.start_with? vp.instance_variable_get(:@path)}&.instance_variable_get(:@path) 61 | current_ext = current_path[/\..*/] 62 | partial_path = "#{current_view_path}#{partial.sub(/\/([^\/]*)$/, '/_\1')}#{current_ext}" 63 | 64 | if (partial_template = TurboPartial::TemplateCache[partial_path]) 65 | partial_template.render self, options[:locals] 66 | else 67 | options[:partial] = partial[1..-1] 68 | super 69 | end 70 | 71 | else 72 | super 73 | end 74 | else 75 | super 76 | end 77 | else 78 | super 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/turbo_partial/template_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboPartial 4 | TemplateCache = Hash.new 5 | 6 | module Template 7 | module TemplateCacheAppender 8 | def initialize(*) 9 | super 10 | 11 | TurboPartial::TemplateCache[identifier] = self 12 | end 13 | end 14 | end 15 | 16 | module LookupContext 17 | module TemplateCacheClearer 18 | def clear 19 | super 20 | 21 | TurboPartial::TemplateCache.clear 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy_app/app/views/admin/beers/index.html.erb: -------------------------------------------------------------------------------- 1 |