Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save raghubetina/b34244e2df90180e6050413589141877 to your computer and use it in GitHub Desktop.
Save raghubetina/b34244e2df90180e6050413589141877 to your computer and use it in GitHub Desktop.
Rails template for creating LTI apps (as mountable rails engines)
# rails plugin new my_lti_app -T --mountable --dummy-path=spec/test_app -m URL_TO_THIS_RAW_GIST
def ask_wizard(question)
ask "\033[1m\033[30m\033[46m" + "prompt".rjust(10) + "\033[0m\033[36m" + " #{question}\033[0m"
end
def yes_wizard?(question)
answer = ask_wizard(question + " \033[33m(y/n)\033[0m")
case answer.downcase
when "yes", "y"
true
when "no", "n"
false
else
yes_wizard?(question)
end
end
def no_wizard?(question); !yes_wizard?(question) end
@opts = {}
@opts[:ember] = yes_wizard?("Would you like to include Ember?")
@opts[:bootstrap] = yes_wizard?("Would you like to include Twitter Bootstrap?")
@opts[:homework_submission] = yes_wizard?("Would you like to use this tool to submit homework?")
@opts[:editor_button] = yes_wizard?("Would you like to add the tool to canvas' rich text editor?")
@opts[:resource_selection] = yes_wizard?("would you like to add the tool to canvas' resource selector?")
@opts[:account_navigation] = yes_wizard?("Would you like to add the tool to account level navigation in canvas?")
@opts[:course_navigation] = yes_wizard?("Would you like to add the tool to course level navigation in canvas?")
@opts[:user_navigation] = yes_wizard?("Would you like to add the tool to user level navigation in canvas?")
if @opts[:ember]
inside(".") do
run "originate ember ember_app"
end
gsub_file "ember_app/Gruntfile.coffee", "build/application.js", "../app/assets/javascripts/#{name}/ember_app.js"
inside("ember_app") do
run "grunt build"
end
end
inject_into_file "app/controllers/#{name}/application_controller.rb", after: "ActionController::Base\n" do <<-'RUBY'
before_action :set_default_headers
def set_default_headers
response.headers['X-Frame-Options'] = 'ALLOWALL'
end
RUBY
end
inject_into_file "#{name}.gemspec", after: "s.add_development_dependency \"sqlite3\"\n" do <<-'RUBY'
s.add_dependency "ims-lti"
s.add_development_dependency "rspec-rails"
s.add_development_dependency "capybara"
s.add_development_dependency "poltergeist"
RUBY
end
if @opts[:bootstrap]
gem 'sass-rails', '>= 3.2'
gem 'bootstrap-sass', '~> 3.1.0'
end
gem_group :development, :test do
gem "jazz_hands"
end
run "bundle install"
generate "rspec:install"
append_file ".gitignore" do <<-'RUBY'
spec/test_app/db/*.sqlite3
spec/test_app/db/*.sqlite3-journal
spec/test_app/log/*.log
spec/test_app/tmp/
spec/test_app/.sass-cache
spec/test_app/config/lti_public_resources_config.yml
spec/test_app/config/*.yml
# ember app
/ember-app/tmp
/ember-app/dist
/ember-app/coverage/*
/ember-app/bower_components/*
/ember-app/node_modules
/ember-app/vendor/*
!/ember-app/vendor/loader.js
RUBY
end
append_file "Rakefile" do <<-'RUBY'
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
RUBY
end
inject_into_file "lib/#{name}/engine.rb", after: "isolate_namespace #{name.classify}\n" do <<-"RUBY"
config.generators do |g|
g.test_framework :rspec
end
RUBY
end
gsub_file "spec/spec_helper.rb", "../../config/environment", "../test_app/config/environment"
inject_into_file "spec/spec_helper.rb", after: "require 'rspec/autorun'\n" do <<-"RUBY"
require 'capybara/rspec'
require 'capybara/rails'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
RUBY
end
generate "controller test backdoor"
remove_file "app/views/#{name}/test/backdoor.html.erb"
create_file "app/views/#{name}/test/backdoor.html.erb" do <<-'RUBY'
<p>Form to access root page via POST. Used for tests.</p>
<%= form_tag root_path do %>
<button type="submit" id="submit">Submit</button>
<% end %>
RUBY
end
route 'root "lti#index"'
route 'match "/" => "lti#index", via: [:get, :post]'
route 'get "health_check" => "lti#health_check"'
route 'get "config(.xml)" => "lti#xml_config", as: :lti_xml_config'
route 'post "embed" => "lti#embed", as: :lti_embed'
generate "controller lti index"
remove_file "app/views/#{name}/lti/index.html.erb"
create_file "app/views/#{name}/lti/index.html.erb" do <<-'RUBY'
<div class="container">
<h4 class="page-header">
<%= link_to "config.xml", lti_xml_config_path, class: "btn btn-xs btn-primary pull-right" %>
<%= image_tag "NAME/icon.png" %> My LTI App
</h4>
<p>
This is an example of how you can select a resource and have it be embedded via LTI.
</p>
<%= form_tag lti_embed_path do %>
<input type="hidden" name="launch_params" value="<%= @launch_params.to_json %>" />
<div class="row">
<div class="col-xs-6">
<input type="submit" name="embed_type" class="btn btn-large btn-info btn-block"
value="Embed a Video" />
</div>
<div class="col-xs-6">
<input type="submit" name="embed_type" class="btn btn-large btn-success btn-block"
value="Embed a Picture" />
</div>
</div>
<% end %>
</div>
RUBY
end
gsub_file "app/views/#{name}/lti/index.html.erb", "NAME", name
create_file "app/views/#{name}/lti/embed.html.erb" do <<-'RUBY'
<div class="container">
<h4 class="page-header">Embed Code Generated</h4>
<p>
You're not in a system that supports auto-inserting content,
so you'll need to copy and past the following code by hand
in order to insert it into your content.
</p>
<textarea class="form-control" rows="4"><iframe title="<%= @title %>" width="<%= @width %>" height="<%= @height %>" src="<%= @url %>" /></textarea>
</div>
RUBY
end
gsub_file "spec/controllers/#{name}/test_controller_spec.rb", "get 'backdoor'\n", "get 'backdoor', use_route: :#{name}\n"
create_file "spec/features/#{name}/workflow_spec.rb" do <<-"RUBY"
require 'spec_helper'
describe 'Workflow', type: :request, js: true do
it 'app should be accessible via POST' do
visit '/#{name}/test/backdoor'
click_button('Submit')
expect(page).to have_content 'My LTI App'
end
end
RUBY
end
inject_into_file "app/controllers/#{name}/lti_controller.rb", after: "require_dependency \"#{name}/application_controller\"\n" do <<-"RUBY"
require "ims/lti"
RUBY
end
gsub_file "spec/controllers/#{name}/lti_controller_spec.rb", "get 'index'\n", "get 'index', use_route: :#{name}\n"
additional_configs = []
additional_configs << " tc.canvas_homework_submission!(enabled: true)" if @opts[:homework_submission]
additional_configs << " tc.canvas_editor_button!(enabled: true)" if @opts[:editor_button]
additional_configs << " tc.canvas_resource_selection!(enabled: true)" if @opts[:resource_selection]
additional_configs << " tc.canvas_account_navigation!(enabled: true)" if @opts[:account_navigation]
additional_configs << " tc.canvas_course_navigation!(enabled: true)" if @opts[:course_navigation]
additional_configs << " tc.canvas_user_navigation!(enabled: true)" if @opts[:user_navigation]
inject_into_file "app/controllers/#{name}/lti_controller.rb", after: "def index\n" do <<-"RUBY"
@launch_params = params.reject!{ |k,v| ['controller','action'].include? k }
end
def embed
launch_params = JSON.parse(params[:launch_params] || "{}")
tp = IMS::LTI::ToolProvider.new(nil, nil, launch_params)
tp.extend IMS::LTI::Extensions::Content::ToolProvider
# The following code is used as an example of content being returned.
# This should be replaced by actual logic.
embed_type = params[:embed_type]
if embed_type =~ /Video/
@title = "Getting Started in Canvas Network"
@url = "//player.vimeo.com/video/79702646"
@width = "500"
@height = "284"
elsif embed_type =~ /Picture/
@title = "Laughing Dog"
@url = "https://dl.dropboxusercontent.com/u/2176587/laughing_dog.jpeg"
@width = "284"
@height = "177"
else
@title = "My Lti App"
@url = "\#{root_url}" # <-- build return URL
@width = 500 # <-- modal width (if applicable)
@height = 530 # <-- modal height (if applicable)
end
redirect_url = build_url(tp, @title, @url, @width, @height)
if redirect_url.present?
redirect_to redirect_url
end
end
def xml_config
host = "\#{request.protocol}\#{request.host_with_port}"
url = "\#{host}\#{root_path}"
title = "#{name.humanize.titleize}"
tool_id = "#{name}"
tc = IMS::LTI::ToolConfig.new(:title => title, :launch_url => url)
tc.extend IMS::LTI::Extensions::Canvas::ToolConfig
tc.description = "[description goes here]"
tc.canvas_privacy_anonymous!
tc.canvas_domain!(request.host)
tc.canvas_icon_url!("\#{host}/assets/#{name}/icon.png")
tc.canvas_text!(title)
tc.set_ext_param('canvas.instructure.com', :tool_id, tool_id)
#{additional_configs.join("\n")}
render xml: tc.to_xml
end
def health_check
head 200
end
private
def build_url(tp, title, url, width, height)
if tp.accepts_content?
if tp.accepts_iframe?
redirect_url = tp.iframe_content_return_url(url, width, height, title)
elsif tp.accepts_url?
redirect_url = tp.url_content_return_url(url, title)
elsif tp.accepts_lti_launch_url?
redirect_url = tp.lti_launch_content_return_url(url, title, title)
end
return redirect_url
end
RUBY
end
inside("app/assets/images/#{name}") do
run "curl -O https://dl.dropboxusercontent.com/u/2176587/icon.png"
end
if @opts[:bootstrap]
remove_file "app/assets/stylesheets/#{name}/application.css"
create_file "app/assets/stylesheets/#{name}/application.scss" do <<-'RUBY'
@import "bootstrap";
RUBY
end
end
inject_into_file "spec/controllers/#{name}/lti_controller_spec.rb", after: "response.should be_success\n end\n" do <<-"RUBY"
describe "GET config" do
it "should generate a valid xml cartridge" do
request.stub(:env).and_return({
"SCRIPT_NAME" => "/#{name}",
"rack.url_scheme" => "http",
"HTTP_HOST" => "test.host",
"PATH_INFO" => "/#{name}"
})
get 'xml_config', use_route: :#{name}
expect(response.body).to include('<blti:title>#{name.humanize.titleize}</blti:title>')
expect(response.body).to include('<blti:description>[description goes here]</blti:description>')
expect(response.body).to include('<lticm:property name="text">#{name.humanize.titleize}</lticm:property>')
expect(response.body).to include('<lticm:property name="tool_id">#{name}</lticm:property>')
expect(response.body).to include('<lticm:property name=\"icon_url\">http://test.host/assets/#{name}/icon.png</lticm:property>')
end
end
RUBY
end
extra_expects = ["\n"]
extra_expects << " expect(response.body).to include('<lticm:options name=\"homework_submission\">')" if @opts[:homework_submission]
extra_expects << " expect(response.body).to include('<lticm:options name=\"editor_button\">')" if @opts[:editor_button]
extra_expects << " expect(response.body).to include('<lticm:options name=\"resource_selection\">')" if @opts[:resource_selection]
extra_expects << " expect(response.body).to include('<lticm:options name=\"account_navigation\">')" if @opts[:account_navigation]
extra_expects << " expect(response.body).to include('<lticm:options name=\"course_navigation\">')" if @opts[:course_navigation]
extra_expects << " expect(response.body).to include('<lticm:options name=\"user_navigation\">')" if @opts[:user_navigation]
inject_into_file "spec/controllers/#{name}/lti_controller_spec.rb", extra_expects.join("\n"), after: "icon.png</lticm:property>')"
inject_into_file "Rakefile", after: "load 'rails/tasks/engine.rake'\n" do
<<-TASK
namespace :engine do
task :run do
exec "cd spec/test_app && bundle exec rails s"
end
end
TASK
end
rake "spec"
puts "To run your LTI application, run `bundle exec rails s` from inside the `spec/test_app` directory."
if yes_wizard?("Would you like to start the server?")
rake "engine:run"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment