-
-
Save raghubetina/b34244e2df90180e6050413589141877 to your computer and use it in GitHub Desktop.
Rails template for creating LTI apps (as mountable rails engines)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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