Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a1782bde3f72927829652c6a15ae6c6469be5662 0 parents
@svenfuchs svenfuchs authored
Showing with 2,073 additions and 0 deletions.
  1. +7 −0 .gitignore
  2. +30 −0 Gemfile
  3. +207 −0 Gemfile.lock
  4. +7 −0 README
  5. +7 −0 Rakefile
  6. BIN  app/assets/images/llama.jpg
  7. BIN  app/assets/images/rails.png
  8. +9 −0 app/assets/javascripts/application.js
  9. +34 −0 app/assets/javascripts/subscriptions.js.coffee
  10. +86 −0 app/assets/stylesheets/application.css.scss
  11. +4 −0 app/controllers/application_controller.rb
  12. +2 −0  app/controllers/home_controller.rb
  13. +12 −0 app/controllers/profile_controller.rb
  14. +20 −0 app/controllers/sessions_controller.rb
  15. +50 −0 app/controllers/subscriptions_controller.rb
  16. +2 −0  app/helpers/application_helper.rb
  17. +2 −0  app/helpers/plans_helper.rb
  18. +2 −0  app/helpers/subscriptions_helper.rb
  19. 0  app/mailers/.gitkeep
  20. 0  app/models/.gitkeep
  21. +2 −0  app/models/address.rb
  22. +3 −0  app/models/payment.rb
  23. +23 −0 app/models/subscription.rb
  24. +44 −0 app/models/user.rb
  25. +2 −0  app/views/home/index.html.haml
  26. +16 −0 app/views/layouts/application.html.erb
  27. +5 −0 app/views/payments/_payments.html.haml
  28. +26 −0 app/views/profile/show.html.haml
  29. +4 −0 app/views/subscriptions/_subscription.html.haml
  30. +69 −0 app/views/subscriptions/new.html.haml
  31. +35 −0 app/views/subscriptions/show.html.haml
  32. +4 −0 config.ru
  33. +48 −0 config/application.rb
  34. +6 −0 config/boot.rb
  35. +8 −0 config/cucumber.yml
  36. +5 −0 config/environment.rb
  37. +32 −0 config/environments/development.rb
  38. +60 −0 config/environments/production.rb
  39. +39 −0 config/environments/test.rb
  40. +7 −0 config/initializers/backtrace_silencers.rb
  41. +10 −0 config/initializers/devise.rb
  42. +10 −0 config/initializers/inflections.rb
  43. +5 −0 config/initializers/mime_types.rb
  44. +7 −0 config/initializers/secret_token.rb
  45. +8 −0 config/initializers/session_store.rb
  46. +111 −0 config/initializers/simple_form.rb
  47. +2 −0  config/initializers/stripe.rb
  48. +14 −0 config/initializers/wrap_parameters.rb
  49. +10 −0 config/locales/en.yml
  50. +23 −0 config/locales/simple_form.yml
  51. +12 −0 config/routes.rb
  52. +7 −0 config/settings.example.yml
  53. +9 −0 db/migrate/20111008164003_create_subscriptions.rb
  54. +16 −0 db/migrate/20111227224127_create_users.rb
  55. +14 −0 db/migrate/20111228000327_create_addresses.rb
  56. +10 −0 db/migrate/20111228175101_create_payments.rb
  57. +57 −0 db/schema.rb
  58. +2 −0  doc/README_FOR_APP
  59. +44 −0 features/ordering_subscriptions.feature
  60. +8 −0 features/step_definitions/authentication_steps.rb
  61. +7 −0 features/step_definitions/common_steps.rb
  62. +3 −0  features/step_definitions/debug_steps.rb
  63. +9 −0 features/step_definitions/ordering_steps.rb
  64. +239 −0 features/step_definitions/web_steps.rb
  65. +56 −0 features/support/env.rb
  66. +28 −0 features/support/oauth.rb
  67. +10 −0 features/support/paths.rb
  68. 0  features/support/selectors.rb
  69. 0  lib/assets/.gitkeep
  70. 0  lib/tasks/.gitkeep
  71. +65 −0 lib/tasks/cucumber.rake
  72. +26 −0 public/404.html
  73. +26 −0 public/422.html
  74. +26 −0 public/500.html
  75. 0  public/favicon.ico
  76. +5 −0 public/robots.txt
  77. +10 −0 script/cucumber
  78. +6 −0 script/rails
  79. +51 −0 spec/fixtures/oauth/github.rb
  80. +82 −0 spec/fixtures/oauth/twitter.rb
  81. 0  vendor/assets/stylesheets/.gitkeep
  82. 0  vendor/plugins/.gitkeep
  83. +20 −0 vendor/plugins/country_select/MIT-LICENSE
  84. +14 −0 vendor/plugins/country_select/README
  85. +1 −0  vendor/plugins/country_select/init.rb
  86. +2 −0  vendor/plugins/country_select/install.rb
  87. +88 −0 vendor/plugins/country_select/lib/country_select.rb
  88. +1 −0  vendor/plugins/country_select/uninstall.rb
7 .gitignore
@@ -0,0 +1,7 @@
+.bundle
+config/database.yml
+config/settings.yml
+db/*.sqlite3
+log/*.log
+tmp/
+.sass-cache/
30 Gemfile
@@ -0,0 +1,30 @@
+source 'http://rubygems.org'
+
+gem 'rails', '~> 3.1.3'
+gem 'rack', '~> 1.3.3'
+gem 'sqlite3'
+
+gem 'jquery-rails'
+
+gem 'stripe'
+gem 'devise', '~> 1.5.0'
+gem 'omniauth-github', '~> 1.0.0'
+gem 'omniauth-twitter'
+gem 'simple_form'
+gem 'haml'
+gem 'hashr'
+
+group :assets do
+ gem 'sass-rails', '~> 3.1.4'
+ gem 'coffee-rails', '~> 3.1.1'
+ gem 'uglifier', '>= 1.0.3'
+end
+
+group :test do
+ gem 'capybara'
+ gem 'database_cleaner'
+ gem 'cucumber-rails'
+ gem 'cucumber'
+ gem 'spork'
+ gem 'launchy'
+end
207 Gemfile.lock
@@ -0,0 +1,207 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ actionmailer (3.1.3)
+ actionpack (= 3.1.3)
+ mail (~> 2.3.0)
+ actionpack (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ i18n (~> 0.6)
+ rack (~> 1.3.5)
+ rack-cache (~> 1.1)
+ rack-mount (~> 0.8.2)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.0.3)
+ activemodel (3.1.3)
+ activesupport (= 3.1.3)
+ builder (~> 3.0.0)
+ i18n (~> 0.6)
+ activerecord (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
+ arel (~> 2.2.1)
+ tzinfo (~> 0.3.29)
+ activeresource (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
+ activesupport (3.1.3)
+ multi_json (~> 1.0)
+ addressable (2.2.6)
+ arel (2.2.1)
+ bcrypt-ruby (3.0.1)
+ builder (3.0.0)
+ capybara (1.1.2)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (~> 2.0)
+ xpath (~> 0.1.4)
+ childprocess (0.2.4)
+ ffi (~> 1.0.6)
+ coffee-rails (3.1.1)
+ coffee-script (>= 2.2.0)
+ railties (~> 3.1.0)
+ coffee-script (2.2.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.2.0)
+ cucumber (1.1.4)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.2)
+ gherkin (~> 2.7.1)
+ json (>= 1.4.6)
+ term-ansicolor (>= 1.0.6)
+ cucumber-rails (1.2.1)
+ capybara (>= 1.1.2)
+ cucumber (>= 1.1.3)
+ nokogiri (>= 1.5.0)
+ database_cleaner (0.7.0)
+ devise (1.5.2)
+ bcrypt-ruby (~> 3.0)
+ orm_adapter (~> 0.0.3)
+ warden (~> 1.1)
+ diff-lcs (1.1.3)
+ erubis (2.7.0)
+ execjs (1.2.13)
+ multi_json (~> 1.0)
+ faraday (0.7.5)
+ addressable (~> 2.2.6)
+ multipart-post (~> 1.1.3)
+ rack (< 2, >= 1.1.0)
+ ffi (1.0.11)
+ gherkin (2.7.1)
+ json (>= 1.4.6)
+ haml (3.1.4)
+ hashie (1.2.0)
+ hashr (0.0.19)
+ hike (1.2.1)
+ i18n (0.6.0)
+ jquery-rails (1.0.19)
+ railties (~> 3.0)
+ thor (~> 0.14)
+ json (1.6.4)
+ launchy (2.0.5)
+ addressable (~> 2.2.6)
+ mail (2.3.0)
+ i18n (>= 0.4.0)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.17.2)
+ multi_json (1.0.4)
+ multipart-post (1.1.4)
+ nokogiri (1.5.0)
+ oauth (0.4.5)
+ oauth2 (0.5.1)
+ faraday (~> 0.7.4)
+ multi_json (~> 1.0.3)
+ omniauth (1.0.1)
+ hashie (~> 1.2)
+ rack
+ omniauth-github (1.0.1)
+ omniauth (~> 1.0)
+ omniauth-oauth2 (~> 1.0)
+ omniauth-oauth (1.0.0)
+ oauth
+ omniauth (~> 1.0)
+ omniauth-oauth2 (1.0.0)
+ oauth2 (~> 0.5.0)
+ omniauth (~> 1.0)
+ omniauth-twitter (0.0.7)
+ omniauth-oauth (~> 1.0)
+ orm_adapter (0.0.5)
+ polyglot (0.3.3)
+ rack (1.3.5)
+ rack-cache (1.1)
+ rack (>= 0.4)
+ rack-mount (0.8.3)
+ rack (>= 1.0.0)
+ rack-ssl (1.3.2)
+ rack
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ rails (3.1.3)
+ actionmailer (= 3.1.3)
+ actionpack (= 3.1.3)
+ activerecord (= 3.1.3)
+ activeresource (= 3.1.3)
+ activesupport (= 3.1.3)
+ bundler (~> 1.0)
+ railties (= 3.1.3)
+ railties (3.1.3)
+ actionpack (= 3.1.3)
+ activesupport (= 3.1.3)
+ rack-ssl (~> 1.3.2)
+ rake (>= 0.8.7)
+ rdoc (~> 3.4)
+ thor (~> 0.14.6)
+ rake (0.9.2.2)
+ rdoc (3.12)
+ json (~> 1.4)
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
+ rubyzip (0.9.5)
+ sass (3.1.12)
+ sass-rails (3.1.5)
+ actionpack (~> 3.1.0)
+ railties (~> 3.1.0)
+ sass (~> 3.1.10)
+ tilt (~> 1.3.2)
+ selenium-webdriver (2.15.0)
+ childprocess (>= 0.2.1)
+ ffi (~> 1.0.9)
+ multi_json (~> 1.0.4)
+ rubyzip
+ simple_form (1.5.2)
+ actionpack (~> 3.0)
+ activemodel (~> 3.0)
+ spork (0.8.5)
+ sprockets (2.0.3)
+ hike (~> 1.2)
+ rack (~> 1.0)
+ tilt (!= 1.3.0, ~> 1.1)
+ sqlite3 (1.3.5)
+ stripe (1.5.25)
+ rest-client (~> 1.4)
+ term-ansicolor (1.0.7)
+ thor (0.14.6)
+ tilt (1.3.3)
+ treetop (1.4.10)
+ polyglot
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.31)
+ uglifier (1.2.1)
+ execjs (>= 0.3.0)
+ multi_json (>= 1.0.2)
+ warden (1.1.0)
+ rack (>= 1.0)
+ xpath (0.1.4)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ capybara
+ coffee-rails (~> 3.1.1)
+ cucumber
+ cucumber-rails
+ database_cleaner
+ devise (~> 1.5.0)
+ haml
+ hashr
+ jquery-rails
+ launchy
+ omniauth-github (~> 1.0.0)
+ omniauth-twitter
+ rack (~> 1.3.3)
+ rails (~> 3.1.3)
+ sass-rails (~> 3.1.4)
+ simple_form
+ spork
+ sqlite3
+ stripe
+ uglifier (>= 1.0.3)
7 README
@@ -0,0 +1,7 @@
+= RailsCasts Example Application
+
+Run these commands to try it out.
+
+ bundle
+ rake db:setup
+ rails s
7 Rakefile
@@ -0,0 +1,7 @@
+#!/usr/bin/env rake
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+
+Saas::Application.load_tasks
BIN  app/assets/images/llama.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  app/assets/images/rails.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 app/assets/javascripts/application.js
@@ -0,0 +1,9 @@
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+//= require jquery
+//= require jquery_ujs
+//= require_tree .
34 app/assets/javascripts/subscriptions.js.coffee
@@ -0,0 +1,34 @@
+$ ->
+ Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'))
+ subscription.setupForm()
+
+subscription =
+ setupForm: ->
+ $('#toggle_shipping_address').change(subscription.toggleShippingAddress)
+ $('#new_subscription').submit ->
+ $('input[type=submit]').attr('disabled', true)
+ if $('#card_number').length
+ subscription.processCard()
+ false
+ else
+ true
+
+ toggleShippingAddress: ->
+ element = $('#shipping_address')
+ if element.is(':visible') then element.hide() else element.show()
+
+ processCard: ->
+ card =
+ number: $('#card_number').val()
+ cvc: $('#card_code').val()
+ expMonth: $('#card_month').val()
+ expYear: $('#card_year').val()
+ Stripe.createToken(card, subscription.handleStripeResponse)
+
+ handleStripeResponse: (status, response) ->
+ if status == 200
+ $('#subscription_stripe_card_token').val(response.id)
+ $('#new_subscription')[0].submit()
+ else
+ $('#stripe_error').text(response.error.message)
+ $('input[type=submit]').attr('disabled', false)
86 app/assets/stylesheets/application.css.scss
@@ -0,0 +1,86 @@
+/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *= require_self
+ *= require_tree .
+*/
+
+html, body {
+ font-family: Verdana, Helvetica, Arial;
+ font-size: 14px;
+}
+
+a img {
+ border: none;
+}
+
+a {
+ color: #0000FF;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.clear {
+ clear: both;
+ height: 0;
+ overflow: hidden;
+}
+
+#container {
+ width: 75%;
+ margin: 0 auto;
+ background-color: #FFF;
+ padding: 20px 40px;
+ border: solid 1px black;
+ margin-top: 20px;
+}
+
+#flash_notice, #flash_error, #flash_alert {
+ padding: 5px 8px;
+ margin: 10px 0;
+}
+
+#flash_notice {
+ background-color: #CFC;
+ border: solid 1px #6C6;
+}
+
+#flash_error, #flash_alert {
+ background-color: #FCC;
+ border: solid 1px #C66;
+}
+
+form label {
+ display: block;
+ margin-bottom: 2px;
+}
+
+form .input, form .actions {
+ margin: 12px 0;
+}
+form .error {
+ color: red;
+}
+
+#subscription {
+ width: 400px;
+ h1 {
+ font-size: 24px;
+ }
+}
+
+#stripe_error {
+ color: #D00;
+}
+
+#new_subscription {
+ #shipping_address {
+ display: none;
+ }
+}
+
+
4 app/controllers/application_controller.rb
@@ -0,0 +1,4 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+end
+
2  app/controllers/home_controller.rb
@@ -0,0 +1,2 @@
+class HomeController < ApplicationController
+end
12 app/controllers/profile_controller.rb
@@ -0,0 +1,12 @@
+class ProfileController < ApplicationController
+ protected
+ helper_method :subscriptions, :payments
+
+ def subscriptions
+ current_user.subscriptions
+ end
+
+ def payments
+ current_user.payments
+ end
+end
20 app/controllers/sessions_controller.rb
@@ -0,0 +1,20 @@
+class SessionsController < Devise::OmniauthCallbacksController
+ def create
+ sign_in_and_redirect User.find_or_create_from_oauth(auth_hash)
+ rescue ActiveRecord::RecordInvalid => e
+ flash[:error] = "Can not sign in with that account: #{e.message}"
+ redirect_to after_sign_in_path_for(:user)
+ end
+ alias :github :create # hu?
+ alias :twitter :create
+
+ def after_omniauth_failure_path_for(*)
+ '/'
+ end
+
+ protected
+
+ def auth_hash
+ request.env['omniauth.auth']
+ end
+end
50 app/controllers/subscriptions_controller.rb
@@ -0,0 +1,50 @@
+class SubscriptionsController < ApplicationController
+ before_filter :normalize_params
+ before_filter :update_user
+
+ def new
+ session["user_return_to"] = "#{request.path}?#{request.query_string}"
+ end
+
+ def create
+ if current_user.valid? && subscription.valid?
+ current_user.save
+ subscription.save # _with_payment
+ redirect_to subscription, :notice => "Thank you for subscribing!"
+ else
+ render :new
+ end
+ end
+
+ protected
+
+ helper_method :current_user, :subscription, :billing_address, :shipping_address
+ delegate :billing_address, :shipping_address, :to => :subscription
+
+ def current_user
+ @current_user ||= super || User.new
+ end
+
+ def subscription
+ @subscription ||= if params[:action].to_sym == :show
+ current_user.subscriptions.find(params[:id])
+ else
+ current_user.subscriptions.build(params[:subscription].except(:user_attributes))
+ end
+ end
+
+ def update_user
+ params[:subscription][:user_attributes].each do |name, value|
+ current_user.send(:"#{name}=", value)
+ end if params.key?(:subscription)
+ end
+
+ def normalize_params
+ params[:subscription] ||= {
+ :plan => params[:plan] || 'tiny',
+ :user_attributes => {},
+ :billing_address_attributes => { :name => current_user.name },
+ :shipping_address_attributes => { :name => current_user.name }
+ }
+ end
+end
2  app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
2  app/helpers/plans_helper.rb
@@ -0,0 +1,2 @@
+module PlansHelper
+end
2  app/helpers/subscriptions_helper.rb
@@ -0,0 +1,2 @@
+module SubscriptionsHelper
+end
0  app/mailers/.gitkeep
No changes.
0  app/models/.gitkeep
No changes.
2  app/models/address.rb
@@ -0,0 +1,2 @@
+class Address < ActiveRecord::Base
+end
3  app/models/payment.rb
@@ -0,0 +1,3 @@
+class Payment < ActiveRecord::Base
+ belongs_to :subscription
+end
23 app/models/subscription.rb
@@ -0,0 +1,23 @@
+class Subscription < ActiveRecord::Base
+ belongs_to :user
+ has_one :billing_address, :class_name => 'Address'
+ has_one :shipping_address, :class_name => 'Address'
+
+ validates_presence_of :plan
+
+ accepts_nested_attributes_for :user, :billing_address, :shipping_address
+
+ attr_accessor :stripe_card_token, :card_number
+
+ def save_with_payment
+ if valid?
+ customer = Stripe::Customer.create(description: user.email, plan: plan, card: stripe_card_token)
+ self.stripe_customer_token = customer.id
+ save!
+ end
+ rescue Stripe::InvalidRequestError => e
+ logger.error "Stripe error while creating customer: #{e.message}"
+ errors.add :base, "There was a problem with your credit card: #{e.message}."
+ false
+ end
+end
44 app/models/user.rb
@@ -0,0 +1,44 @@
+class User < ActiveRecord::Base
+ devise :omniauthable
+ has_many :subscriptions
+ has_many :payments
+
+ validates_presence_of :name, :email, :unless => :from_oauth?
+ validates_uniqueness_of :email, :github_handle, :twitter_handle, :allow_blank => true
+
+ class << self
+ def find_or_create_from_oauth(data)
+ data = Hashr.new(data)
+ send(:"find_or_create_from_#{data.provider}_oauth", data)
+ end
+
+ def find_or_create_from_github_oauth(data)
+ find_or_create_by_github_handle(data.info.nickname) do |user|
+ user.update_attributes!(
+ name: data.info.name,
+ github_uid: data.uid,
+ email: data.info.email,
+ homepage: data.info.urls.Blog,
+ from_oauth: true
+ )
+ end
+ end
+
+ def find_or_create_from_twitter_oauth(data)
+ find_or_create_by_twitter_handle(data.info.nickname) do |user|
+ user.update_attributes(
+ name: data.info.name,
+ twitter_uid: data.uid,
+ homepage: data.info.urls.Website,
+ from_oauth: true
+ )
+ end
+ end
+ end
+
+ attr_accessor :from_oauth
+
+ def from_oauth?
+ !!@from_oauth
+ end
+end
2  app/views/home/index.html.haml
@@ -0,0 +1,2 @@
+#medium
+ = link_to "Donate", new_subscription_path(:plan => 'medium')
16 app/views/layouts/application.html.erb
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Travis CI needs your help</title>
+ <%= stylesheet_link_tag "application" %>
+ <%= javascript_include_tag "https://js.stripe.com/v1/", "application" %>
+ <%= csrf_meta_tags %>
+ <%= tag :meta, :name => "stripe-key", :content => STRIPE_PUBLIC_KEY %>
+</head>
+<body>
+ <% flash.each do |name, msg| %>
+ <%= content_tag :div, msg, :id => "flash_#{name}" %>
+ <% end %>
+ <%= yield %>
+</body>
+</html>
5 app/views/payments/_payments.html.haml
@@ -0,0 +1,5 @@
+%tr
+ %td= payment.created_at.to_formatted_s(:long)
+ %td= link_to payment.subscription.camelize, payment.subscription
+ %td= payment.total
+ %td= link_to 'Invoice', payment
26 app/views/profile/show.html.haml
@@ -0,0 +1,26 @@
+%h2 Your profile
+
+#profile
+ %dl
+ %dt Name
+ %dd= current_user.name
+ %dt Email
+ %dd= current_user.email
+ %dt Twitter
+ %dd= current_user.twitter_handle
+ %dt Github
+ %dd= current_user.github_handle
+ %dt Homepage
+ %dd= current_user.homepage
+
+- unless subscriptions.empty?
+ #subscriptions
+ %h2 Your subscriptions
+ %table
+ = render subscriptions
+
+- unless payments.empty?
+ #payments
+ %h2 Your payments
+ %table
+ = render payments
4 app/views/subscriptions/_subscription.html.haml
@@ -0,0 +1,4 @@
+%tr
+ %td= link_to subscription.plan.camelize, subscription
+ %td= subscription.created_at.to_formatted_s(:long)
+ %td= link_to 'Cancel', subscription, :method => :delete, :alert => 'Are you sure you want to cancel this subscription?'
69 app/views/subscriptions/new.html.haml
@@ -0,0 +1,69 @@
+= link_to 'Sign out', destroy_session_path if signed_in?
+
+%h1= subscription.plan.camelize
+
+= simple_form_for subscription do |f|
+ = f.hidden_field :plan
+ = f.hidden_field :stripe_card_token
+
+ - if signed_in?
+ = f.simple_fields_for :user do |f|
+ = '' # weird, when i remove this and none of the fields below are rendered then the hidden input is html escaped
+
+ - unless current_user.name
+ = f.input :name
+
+ - unless current_user.email
+ = f.input :email
+
+ - unless current_user.twitter_handle
+ = f.input :twitter_handle
+
+ - unless current_user.github_handle
+ = f.input :github_handle
+
+ - unless current_user.homepage
+ = f.input :homepage
+
+ #billing_address.address
+ %h2 Billing Address
+ = f.simple_fields_for :billing_address do |f|
+ = f.input :name
+ = f.input :street
+ = f.input :zip
+ = f.input :city
+ = f.input :state
+ = f.input :country
+ = label_tag :toggle_shipping_address do
+ = check_box_tag 'toggle_shipping_address', 1, true
+ = 'Use as shipping address'
+
+ #shipping_address.address
+ %h2 Shipping Address
+ = f.simple_fields_for :shipping_address do |f|
+ = f.input :name
+ = f.input :street
+ = f.input :zip
+ = f.input :city
+ = f.input :state
+ = f.input :country
+
+ %h2 Credit Card
+ - if subscription.stripe_card_token.present?
+ %p Has been provided.
+ - else
+ = f.input :number, :as => :anonymous, :required => true
+ = f.input :security_code, :as => :anonymous, :required => true
+ = f.input :expires_at, :as => :expiry_selects
+
+ #stripe_error
+ %noscript JavaScript is not enabled and is required for this form. First enable it in your web browser settings.
+
+ .actions
+ = f.submit 'Confirm'
+ = link_to 'Deny', '/'
+
+ - else
+ = link_to 'Sign in with Twitter', user_omniauth_authorize_path(:twitter)
+ = link_to 'Sign in with Github', user_omniauth_authorize_path(:github)
+
35 app/views/subscriptions/show.html.haml
@@ -0,0 +1,35 @@
+%p= link_to '&larr; Your profile'.html_safe, profile_path
+
+#subscription
+ %h2= subscription.plan.camelize
+ %p.meta= subscription.created_at.to_formatted_s(:long)
+
+ %dl
+ %dt Name
+ %dd= current_user.name
+ %dt Email
+ %dd= current_user.email
+ %dt Twitter
+ %dd= current_user.twitter_handle
+ %dt Github
+ %dd= current_user.github_handle
+ %dt Homepage
+ %dd= current_user.homepage
+
+ %h1 Billing Address
+ %dl
+ %dt Street
+ %dd= billing_address.street
+ %dt Zip/City
+ %dd= "#{billing_address.zip} #{billing_address.city}"
+ %dt State/Country
+ %dd= "#{billing_address.state} #{billing_address.country}"
+
+ %h1 Shipping Address
+ %dl
+ %dt Street
+ %dd= shipping_address.street
+ %dt Zip/City
+ %dd= "#{shipping_address.zip} #{shipping_address.city}"
+ %dt State/Country
+ %dd= "#{shipping_address.state} #{shipping_address.country}"
4 config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Saas::Application
48 config/application.rb
@@ -0,0 +1,48 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+
+if defined?(Bundler)
+ # If you precompile assets before deploying to production, use this line
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
+ # If you want your assets lazily compiled in production, use this line
+ # Bundler.require(:default, :assets, Rails.env)
+end
+
+module Saas
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
+
+ # Version of your assets, change this if you want to expire all your assets
+ config.assets.version = '1.0'
+ end
+end
6 config/boot.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
8 config/cucumber.yml
@@ -0,0 +1,8 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip"
+%>
+default: <%= std_opts %> features
+wip: --tags @wip:3 --wip features
+rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
5 config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Saas::Application.initialize!
32 config/environments/development.rb
@@ -0,0 +1,32 @@
+Saas::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ config.log_level = :debug
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+
+ # Do not compress assets
+ config.assets.compress = false
+
+ # Expands the lines which load the assets
+ config.assets.debug = true
+end
60 config/environments/production.rb
@@ -0,0 +1,60 @@
+Saas::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
+
+ # Compress JavaScripts and CSS
+ config.assets.compress = true
+
+ # Don't fallback to assets pipeline if a precompiled asset is missed
+ config.assets.compile = false
+
+ # Generate digests for assets URLs
+ config.assets.digest = true
+
+ # Defaults to Rails.root.join("public/assets")
+ # config.assets.manifest = YOUR_PATH
+
+ # Specifies the header that your server uses for sending files
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # config.assets.precompile += %w( search.js )
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+end
39 config/environments/test.rb
@@ -0,0 +1,39 @@
+Saas::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Log error messages when you accidentally call methods on nil
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
10 config/initializers/devise.rb
@@ -0,0 +1,10 @@
+settings = Hashr.new(YAML.load(File.read('config/settings.yml')))
+
+Devise.setup do |c|
+ require 'devise/orm/active_record'
+
+ c.http_authenticatable = true
+
+ c.omniauth :github, settings.github.client_id, settings.github.client_secret, :scope => settings.github.scope
+ c.omniauth :twitter, settings.twitter.client_id, settings.twitter.client_secret
+end
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
7 config/initializers/secret_token.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+Saas::Application.config.secret_token = '6e2b941713e5b9fa57cdff558772b88dc6dca42e74641bb01b835b0caa70526a0baf3c264a5bb6815cf1f5943ed48499ec66939e8984e0d84beed97b01923c0c'
8 config/initializers/session_store.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+Saas::Application.config.session_store :cookie_store, key: '_saas_session'
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rails generate session_migration")
+# Saas::Application.config.session_store :active_record_store
111 config/initializers/simple_form.rb
@@ -0,0 +1,111 @@
+class AnonymousInput < SimpleForm::Inputs::Base
+ def input
+ add_size!
+ id = "#{@builder.object_name}[#{attribute_name}]" # TODO no idea where to get the generic value from
+ @builder.template.text_field_tag(id, nil, input_html_options.merge(name: nil))
+ end
+
+ def input_html_classes
+ super.unshift("string")
+ end
+end
+
+class ExpirySelectsInput < SimpleForm::Inputs::Base
+ def input
+ @builder.template.select_month(nil, { add_month_numbers: true }, { name: nil, id: 'card_month' }) +
+ @builder.template.select_year(nil, { start_year: Date.today.year, end_year: Date.today.year + 15 }, { name: nil, id: 'card_year' })
+ end
+end
+
+SimpleForm.setup do |config|
+ # Components used by the form builder to generate a complete input. You can remove
+ # any of them, change the order, or even add your own components to the stack.
+ # config.components = [ :placeholder, :label_input, :hint, :error ]
+
+ # Default tag used on hints.
+ # config.hint_tag = :span
+
+ # CSS class to add to all hint tags.
+ # config.hint_class = :hint
+
+ # CSS class used on errors.
+ # config.error_class = :error
+
+ # Default tag used on errors.
+ # config.error_tag = :span
+
+ # Method used to tidy up errors.
+ # config.error_method = :first
+
+ # Default tag used for error notification helper.
+ # config.error_notification_tag = :p
+
+ # CSS class to add for error notification helper.
+ # config.error_notification_class = :error_notification
+
+ # ID to add for error notification helper.
+ # config.error_notification_id = nil
+
+ # You can wrap all inputs in a pre-defined tag.
+ # config.wrapper_tag = :div
+
+ # CSS class to add to all wrapper tags.
+ # config.wrapper_class = :input
+
+ # CSS class to add to the wrapper if the field has errors.
+ # config.wrapper_error_class = :field_with_errors
+
+ # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
+ # config.collection_wrapper_tag = nil
+
+ # You can wrap each item in a collection of radio/check boxes with a tag, defaulting to span.
+ # config.item_wrapper_tag = :span
+
+ # Series of attempts to detect a default label method for collection.
+ # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
+
+ # Series of attempts to detect a default value method for collection.
+ # config.collection_value_methods = [ :id, :to_s ]
+
+ # How the label text should be generated altogether with the required text.
+ config.label_text = lambda { |label, required| label }
+
+ # You can define the class to use on all labels. Default is nil.
+ # config.label_class = nil
+
+ # You can define the class to use on all forms. Default is simple_form.
+ # config.form_class = :simple_form
+
+ # Whether attributes are required by default (or not). Default is true.
+ # config.required_by_default = true
+
+ # Tell browsers whether to use default HTML5 validations (novalidate option).
+ # Default is enabled.
+ # config.browser_validations = true
+
+ # Determines whether HTML5 types (:email, :url, :search, :tel) and attributes
+ # (e.g. required) are used or not. True by default.
+ # Having this on in non-HTML5 compliant sites can cause odd behavior in
+ # HTML5-aware browsers such as Chrome.
+ # config.html5 = true
+
+ # Custom mappings for input types. This should be a hash containing a regexp
+ # to match as key, and the input type that will be used when the field name
+ # matches the regexp as value.
+ # config.input_mappings = { /count/ => :integer }
+
+ # Collection of methods to detect if a file type was given.
+ # config.file_methods = [ :mounted_as, :file?, :public_filename ]
+
+ # Default priority for time_zone inputs.
+ # config.time_zone_priority = nil
+
+ # Default priority for country inputs.
+ # config.country_priority = nil
+
+ # Default size for text inputs.
+ # config.default_input_size = 50
+
+ # When false, do not use translations for labels, hints or placeholders.
+ # config.translate = true
+end
2  config/initializers/stripe.rb
@@ -0,0 +1,2 @@
+Stripe.api_key = "tGaNWsIG3Qy6zvXB8wv4rEcizJpSXjF4"
+STRIPE_PUBLIC_KEY = "pk_KcSyS2qPWdT5SdrwkQg0vNSyhZgqP"
14 config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
10 config/locales/en.yml
@@ -0,0 +1,10 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
+
+devise:
+ sessions:
+ user:
+ failure: "Sorry, could not sign in"
23 config/locales/simple_form.yml
@@ -0,0 +1,23 @@
+en:
+ simple_form:
+ "yes": 'Yes'
+ "no": 'No'
+ required:
+ text: 'required'
+ mark: ''
+ # You can uncomment the line below if you need to overwrite the whole required html.
+ # When using html, text and mark won't be used.
+ # html: '<abbr title="required">*</abbr>'
+ error_notification:
+ default_message: "Some errors were found, please take a look:"
+ # Labels and hints examples
+ # labels:
+ # password: 'Password'
+ # user:
+ # new:
+ # email: 'E-mail para efetuar o sign in.'
+ # edit:
+ # email: 'E-mail.'
+ # hints:
+ # username: 'User name to sign in.'
+ # password: 'No special characters, please.'
12 config/routes.rb
@@ -0,0 +1,12 @@
+Saas::Application.routes.draw do
+ root to: 'home#index'
+
+ resources :subscriptions
+
+ match '/profile', :to => 'profile#show'
+ devise_for :users, controllers: { omniauth_callbacks: 'sessions' }
+
+ as :user do
+ get 'users/sign_out', to: 'devise/sessions#destroy', as: :destroy_session
+ end
+end
7 config/settings.example.yml
@@ -0,0 +1,7 @@
+twitter:
+ client_id: client_id
+ client_secret: client_secret
+github:
+ client_id: client_id
+ client_secret: client_secret
+
9 db/migrate/20111008164003_create_subscriptions.rb
@@ -0,0 +1,9 @@
+class CreateSubscriptions < ActiveRecord::Migration
+ def change
+ create_table :subscriptions do |t|
+ t.belongs_to :user
+ t.string :plan
+ t.timestamps
+ end
+ end
+end
16 db/migrate/20111227224127_create_users.rb
@@ -0,0 +1,16 @@
+class CreateUsers < ActiveRecord::Migration
+ def change
+ create_table :users do |t|
+ t.string :name
+ t.string :email
+ t.integer :twitter_uid
+ t.string :twitter_handle
+ t.integer :github_uid
+ t.string :github_handle
+ t.string :homepage
+ t.string :description
+ t.string :stripe_customer_token
+ t.timestamps
+ end
+ end
+end
14 db/migrate/20111228000327_create_addresses.rb
@@ -0,0 +1,14 @@
+class CreateAddresses < ActiveRecord::Migration
+ def change
+ create_table :addresses do |t|
+ t.belongs_to :subscription
+ t.string :name
+ t.string :street
+ t.string :zip
+ t.string :city
+ t.string :state
+ t.string :country
+ t.timestamps
+ end
+ end
+end
10 db/migrate/20111228175101_create_payments.rb
@@ -0,0 +1,10 @@
+class CreatePayments < ActiveRecord::Migration
+ def change
+ create_table :payments do |t|
+ t.belongs_to :user
+ t.belongs_to :subscription
+ t.integer :amount
+ t.timestamps
+ end
+ end
+end
57 db/schema.rb
@@ -0,0 +1,57 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20111228175101) do
+
+ create_table "addresses", :force => true do |t|
+ t.integer "subscription_id"
+ t.string "name"
+ t.string "street"
+ t.string "zip"
+ t.string "city"
+ t.string "state"
+ t.string "country"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "payments", :force => true do |t|
+ t.integer "user_id"
+ t.integer "subscription_id"
+ t.integer "amount"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "subscriptions", :force => true do |t|
+ t.integer "user_id"
+ t.string "plan"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "users", :force => true do |t|
+ t.string "name"
+ t.string "email"
+ t.integer "twitter_uid"
+ t.string "twitter_handle"
+ t.integer "github_uid"
+ t.string "github_handle"
+ t.string "homepage"
+ t.string "description"
+ t.string "stripe_customer_token"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+end
2  doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
44 features/ordering_subscriptions.feature
@@ -0,0 +1,44 @@
+Feature: Ordering a Subscription
+ Scenario: Not having an account
+ Given I do not have an account
+ And I am on the home page
+ When I click on "Donate" for the "medium" subscription
+ When I sign in as "svenfuchs" using twitter
+ Then I should see a new subscription form
+ When I fill in the following:
+ | Email | svenfuchs@artweb-design.de |
+ | Github handle | svenfuchs |
+ | Street | Grünberger Str 65 |
+ | Zip | 10245 |
+ | City | Berlin |
+ | Country | Germany |
+ | Number | 123456 |
+ | Security code | 123 |
+ And I press "Confirm"
+ Then I should see "Thank you for subscribing!"
+ And I should see "Medium"
+
+ Scenario: Already having an account
+ Given I have the following account:
+ | name | Sven Fuchs |
+ | twitter_uid | 12345678 |
+ | twitter_handle | svenfuchs |
+ | homepage | http://svenfuchs.com |
+ | description | My bio |
+ And I am on the home page
+ When I click on "Donate" for the "medium" subscription
+ When I sign in as "svenfuchs" using twitter
+ Then I should see a new subscription form
+ When I fill in the following:
+ | Email | svenfuchs@artweb-design.de |
+ | Github handle | svenfuchs |
+ | Street | Grünberger Str 65 |
+ | Zip | 10245 |
+ | City | Berlin |
+ | Country | Germany |
+ | Number | 123456 |
+ | Security code | 123 |
+ And I press "Confirm"
+ Then I should see "Thank you for subscribing!"
+ And I should see "Medium"
+
8 features/step_definitions/authentication_steps.rb
@@ -0,0 +1,8 @@
+Given /^I have the following account:$/ do |data|
+ User.create(data.rows_hash.to_hash)
+end
+
+When /^I sign in as "(.*)" using (.*)$/ do |name, provider|
+ OmniAuth.config.mock_auth[:twitter] = oauth_payload_for(provider, name)
+ visit '/users/auth/twitter'
+end
7 features/step_definitions/common_steps.rb
@@ -0,0 +1,7 @@
+Given /^I do not have an account$/ do
+ # noop
+end
+
+# Given /^I am on the (.*) page$/ do |page|
+# visit path_for(page)
+# end
3  features/step_definitions/debug_steps.rb
@@ -0,0 +1,3 @@
+Then 'output the page' do
+ puts page.body
+end
9 features/step_definitions/ordering_steps.rb
@@ -0,0 +1,9 @@
+When /^I click on "Donate" for the "([^"]*)" subscription$/ do |package|
+ within("##{package}") do
+ click_link('Donate')
+ end
+end
+
+Then /^I should see a new subscription form$/ do
+ page.has_css?('form#new_subscription').should be_true
+end
239 features/step_definitions/web_steps.rb
@@ -0,0 +1,239 @@
+require 'uri'
+require 'cgi'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors"))
+
+module WithinHelpers
+ def with_scope(locator)
+ locator ? within(*selector_for(locator)) { yield } : yield
+ end
+end
+World(WithinHelpers)
+
+# Single-line step scoper
+When /^(.*) within (.*[^:])$/ do |step, parent|
+ with_scope(parent) { When step }
+end
+
+# Multi-line step scoper
+When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string|
+ with_scope(parent) { When "#{step}:", table_or_string }
+end
+
+Given /^(?:|I )am on (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )go to (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )press "([^"]*)"$/ do |button|
+ click_button(button)
+end
+
+When /^(?:|I )follow "([^"]*)"$/ do |link|
+ click_link(link)
+end
+
+When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
+ fill_in(field, :with => value)
+end
+
+When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
+ fill_in(field, :with => value)
+end
+
+# Use this to fill in an entire form with data from a table. Example:
+#
+# When I fill in the following:
+# | Account Number | 5002 |
+# | Expiry date | 2009-11-01 |
+# | Note | Nice guy |
+# | Wants Email? | |
+#
+# TODO: Add support for checkbox, select or option
+# based on naming conventions.
+#
+When /^(?:|I )fill in the following:$/ do |fields|
+ fields.rows_hash.each do |name, value|
+ field = find_field(name)
+ case field.tag_name
+ when 'select'
+ step %{I select "#{value}" from "#{name}"}
+ else
+ step %{I fill in "#{name}" with "#{value}"}
+ end
+ end
+end
+
+When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
+ select(value, :from => field)
+end
+
+When /^(?:|I )check "([^"]*)"$/ do |field|
+ check(field)
+end
+
+When /^(?:|I )uncheck "([^"]*)"$/ do |field|
+ uncheck(field)
+end
+
+When /^(?:|I )choose "([^"]*)"$/ do |field|
+ choose(field)
+end
+
+When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
+ attach_file(field, File.expand_path(path))
+end
+
+Then /^(?:|I )should see "([^"]*)"$/ do |text|
+ if page.respond_to? :should
+ page.should have_content(text)
+ else
+ assert page.has_content?(text)
+ end
+end
+
+Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
+ regexp = Regexp.new(regexp)
+
+ if page.respond_to? :should
+ page.should have_xpath('//*', :text => regexp)
+ else
+ assert page.has_xpath?('//*', :text => regexp)
+ end
+end
+
+Then /^(?:|I )should not see "([^"]*)"$/ do |text|
+ if page.respond_to? :should
+ page.should have_no_content(text)
+ else
+ assert page.has_no_content?(text)
+ end
+end
+
+Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
+ regexp = Regexp.new(regexp)
+
+ if page.respond_to? :should
+ page.should have_no_xpath('//*', :text => regexp)
+ else
+ assert page.has_no_xpath?('//*', :text => regexp)
+ end
+end
+
+Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value|
+ with_scope(parent) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should
+ field_value.should =~ /#{value}/
+ else
+ assert_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value|
+ with_scope(parent) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should_not
+ field_value.should_not =~ /#{value}/
+ else
+ assert_no_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field should have the error "([^"]*)"$/ do |field, error_message|
+ element = find_field(field)
+ classes = element.find(:xpath, '..')[:class].split(' ')
+
+ form_for_input = element.find(:xpath, 'ancestor::form[1]')
+ using_formtastic = form_for_input[:class].include?('formtastic')
+ error_class = using_formtastic ? 'error' : 'field_with_errors'
+
+ if classes.respond_to? :should
+ classes.should include(error_class)
+ else
+ assert classes.include?(error_class)
+ end
+
+ if page.respond_to?(:should)
+ if using_formtastic
+ error_paragraph = element.find(:xpath, '../*[@class="inline-errors"][1]')
+ error_paragraph.should have_content(error_message)
+ else
+ page.should have_content("#{field.titlecase} #{error_message}")
+ end
+ else
+ if using_formtastic
+ error_paragraph = element.find(:xpath, '../*[@class="inline-errors"][1]')
+ assert error_paragraph.has_content?(error_message)
+ else
+ assert page.has_content?("#{field.titlecase} #{error_message}")
+ end
+ end
+end
+
+Then /^the "([^"]*)" field should have no error$/ do |field|
+ element = find_field(field)
+ classes = element.find(:xpath, '..')[:class].split(' ')
+ if classes.respond_to? :should
+ classes.should_not include('field_with_errors')
+ classes.should_not include('error')
+ else
+ assert !classes.include?('field_with_errors')
+ assert !classes.include?('error')
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent|
+ with_scope(parent) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_true
+ else
+ assert field_checked
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent|
+ with_scope(parent) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_false
+ else
+ assert !field_checked
+ end
+ end
+end
+
+Then /^(?:|I )should be on (.+)$/ do |page_name|
+ current_path = URI.parse(current_url).path
+ if current_path.respond_to? :should
+ current_path.should == path_to(page_name)
+ else
+ assert_equal path_to(page_name), current_path
+ end
+end
+
+Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
+ query = URI.parse(current_url).query
+ actual_params = query ? CGI.parse(query) : {}
+ expected_params = {}
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
+
+ if actual_params.respond_to? :should
+ actual_params.should == expected_params
+ else
+ assert_equal expected_params, actual_params
+ end
+end
+
+Then /^show me the page$/ do
+ save_and_open_page
+end
56 features/support/env.rb
@@ -0,0 +1,56 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+require 'cucumber/rails'
+
+# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
+# order to ease the transition to Capybara we set the default here. If you'd
+# prefer to use XPath just remove this line and adjust any selectors in your
+# steps to use the XPath syntax.
+Capybara.default_selector = :css
+
+# By default, any exception happening in your Rails application will bubble up
+# to Cucumber so that your scenario will fail. This is a different from how
+# your application behaves in the production environment, where an error page will
+# be rendered instead.
+#
+# Sometimes we want to override this default behaviour and allow Rails to rescue
+# exceptions and display an error page (just like when the app is running in production).
+# Typical scenarios where you want to do this is when you test your error pages.
+# There are two ways to allow Rails to rescue exceptions:
+#
+# 1) Tag your scenario (or feature) with @allow-rescue
+#
+# 2) Set the value below to true. Beware that doing this globally is not
+# recommended as it will mask a lot of errors for you!
+#
+ActionController::Base.allow_rescue = false
+
+# Remove/comment out the lines below if your app doesn't have a database.
+# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead.
+begin
+ DatabaseCleaner.strategy = :transaction
+rescue NameError
+ raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
+end
+
+# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios.
+# See the DatabaseCleaner documentation for details. Example:
+#
+# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do
+# DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}
+# end
+#
+# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do
+# DatabaseCleaner.strategy = :transaction
+# end
+#
+
+# Possible values are :truncation and :transaction
+# The :transaction strategy is faster, but might give you threading problems.
+# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature
+Cucumber::Rails::Database.javascript_strategy = :truncation
+
28 features/support/oauth.rb
@@ -0,0 +1,28 @@
+# http://pivotallabs.com/users/mgehard/blog/articles/1595-testing-omniauth-based-login-via-cucumber
+
+OmniAuth.config.test_mode = true
+
+module Accounts
+ PAYLOADS = {
+ twitter: {
+ svenfuchs: {
+ provider: 'twitter',
+ uid: '12345678',
+ info: {
+ name: 'Sven Fuchs',
+ nickname: 'svenfuchs',
+ description: 'My bio',
+ urls: {
+ website: 'http://svenfuchs.com'
+ }
+ }
+ }
+ }
+ }
+
+ def oauth_payload_for(provider, name)
+ Hashr.new(PAYLOADS[provider.to_sym][name.to_sym])
+ end
+end
+
+World(Accounts)
10 features/support/paths.rb
@@ -0,0 +1,10 @@
+module Paths
+ def path_to(name)
+ case name
+ when 'the home page'
+ root_path
+ end
+ end
+end
+
+World(Paths)
0  features/support/selectors.rb
No changes.
0  lib/assets/.gitkeep
No changes.
0  lib/tasks/.gitkeep
No changes.
65 lib/tasks/cucumber.rake
@@ -0,0 +1,65 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
+
+vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
+
+begin
+ require 'cucumber/rake/task'
+
+ namespace :cucumber do
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'default'
+ end
+
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'wip'
+ end
+
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'rerun'
+ end
+
+ desc 'Run all features'
+ task :all => [:ok, :wip]
+
+ task :statsetup do
+ require 'rails/code_statistics'
+ ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features')
+ ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features')
+ end
+ end
+ desc 'Alias for cucumber:ok'
+ task :cucumber => 'cucumber:ok'
+
+ task :default => :cucumber
+
+ task :features => :cucumber do
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
+ end
+
+ # In case we don't have ActiveRecord, append a no-op task that we can depend upon.
+ task 'db:test:prepare' do
+ end
+
+ task :stats => 'cucumber:statsetup'
+rescue LoadError
+ desc 'cucumber rake task not available (cucumber not installed)'
+ task :cucumber do
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
+ end
+end
+
+end
26 public/404.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
26 public/422.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
26 public/500.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
+ </div>
+</body>
+</html>
0  public/favicon.ico
No changes.
5 public/robots.txt
@@ -0,0 +1,5 @@
+# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-Agent: *
+# Disallow: /
10 script/cucumber
@@ -0,0 +1,10 @@
+#!/usr/bin/env ruby
+
+vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+if vendored_cucumber_bin
+ load File.expand_path(vendored_cucumber_bin)
+else
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
+ require 'cucumber'
+ load Cucumber::BINARY
+end
6 script/rails
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
+
+APP_PATH = File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
+require 'rails/commands'
51 spec/fixtures/oauth/github.rb
@@ -0,0 +1,51 @@
+{
+ 'provider' => 'github',
+ 'uid' => 2208,
+ 'info' => {
+ 'nickname' => 'svenfuchs',
+ 'email' => 'svenfuchs@artweb-design.de',
+ 'name' => 'Sven Fuchs',
+ 'urls' => {
+ 'GitHub' => 'https://github.com/svenfuchs',
+ 'Blog' => 'http://svenfuchs.com'
+ }
+ },
+ 'credentials' => {
+ 'token' => '1234567',
+ 'expires' => false
+ },
+ 'extra' => {
+ 'raw_info' => {
+ 'collaborators' => 16,
+ 'created_at' => '2008-03-04T20:38:09Z',
+ 'type' => 'User',
+ 'gravatar_id' => '402602a60e500e85f2f5dc1ff3648ecb',
+ 'disk_usage' => 81148,
+ 'total_private_repos' => 2,
+ 'public_repos' => 85,
+ 'blog' => 'http://svenfuchs.com',
+ 'plan' => {
+ 'collaborators' => 5,
+ 'space' => 1228800,
+ 'private_repos' => 10,
+ 'name' => 'small'
+ },
+ 'public_gists' => 77,
+ 'owned_private_repos' => 2,
+ 'following' => 87,
+ 'hireable' => false,
+ 'private_gists' => 407,
+ 'followers' => 234,
+ 'bio' => nil,
+ 'avatar_url' => 'https://secure.gravatar.com/avatar/402602a60e500e85f2f5dc1ff3648ecb?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png',
+ 'html_url' => 'https://github.com/svenfuchs',
+ 'name' => 'Sven Fuchs',
+ 'location' => 'Germany/Berlin',
+ 'company' => nil,
+ 'email' => 'svenfuchs@artweb-design.de',
+ 'url' => 'https://api.github.com/users/svenfuchs',
+ 'id' => 2208,
+ 'login' => 'svenfuchs'
+ }
+ }
+}
82 spec/fixtures/oauth/twitter.rb
@@ -0,0 +1,82 @@
+{
+ :provider => "twitter",
+ :uid => "9459332",
+ :info => {
+ :nickname => "svenfuchs",
+ :name => "Sven Fuchs",
+ :location => "Berlin",
+ :image => "http://a0.twimg.com/profile_images/301443535/sven.twitter_normal.jpg",
+ :description => "",
+ :urls => {
+ :Website => "http://svenfuchs.com",
+ :Twitter => "http://twitter.com/svenfuchs"
+ }
+ },
+ :credentials => {
+ :token => "9459332-4wSrusxutWD2cpkhNJ1vi89CWZFVvZ2zG7kqN6cTzL",
+ :secret => "hzqCIsifsCbGxHrxq5YgxUERiHuK0mLNNWzMcyVXyKY"
+ },
+ :extra => {
+ :raw_info => {
+ :listed_count => 161,
+ :lang => "en",
+ :protected => false,
+ :url => "http://svenfuchs.com",
+ :verified => false,
+ :time_zone => "Berlin",
+ :profile_sidebar_border_color => "C0DEED",
+ :name => "Sven Fuchs",
+ :id_str => "9459332",
+ :default_profile => true,
+ :contributors_enabled => false,
+ :profile_use_background_image => true,
+ :utc_offset => 3600,
+ :description => "",
+ :default_profile_image => false,
+ :created_at => "Mon Oct 15 17:37:10 +0000 2007",
+ :friends_count => 662,
+ :profile_text_color => "333333",
+ :statuses_count => 4839,
+ :following => false,
+ :profile_background_image_url => "http://a0.twimg.com/images/themes/theme1/bg.png",
+ :status => {
+ :in_reply_to_user_id_str => "14506011",
+ :place => nil,
+ :id_str => "151824978264850434",
+ :in_reply_to_status_id => 151822638287831040,
+ :contributors => nil,
+ :geo => nil,
+ :truncated => false,
+ :created_at => "Wed Dec 28 00:41:10 +0000 2011",
+ :favorited => false,
+ :in_reply_to_user_id => 14506011,
+ :retweet_count => 0,
+ :in_reply_to_screen_name => "ryanbigg",
+ :source => "<a href=\"http://www.echofon.com/\" rel=\"nofollow\">Echofon</a>",
+ :in_reply_to_status_id_str => "151822638287831040",
+ :coordinates => nil,
+ :id => 151824978264850434,
+ :possibly_sensitive => false,
+ :retweeted => false,
+ :text => "@ryanbigg @steveklabnik here's how travis-ci did it on backbone https://t.co/ggNiWvUX"
+ },
+ :is_translator => false,
+ :profile_link_color => "0084B4",
+ :profile_background_image_url_https => "https://si0.twimg.com/images/themes/theme1/bg.png",
+ :favourites_count => 4,
+ :screen_name => "svenfuchs",
+ :follow_request_sent => false,
+ :geo_enabled => false,
+ :profile_background_color => "C0DEED",
+ :notifications => false,
+ :profile_background_tile => false,
+ :followers_count => 1793,
+ :profile_image_url => "http://a0.twimg.com/profile_images/301443535/sven.twitter_normal.jpg",
+ :location => "Berlin",
+ :id => 9459332,
+ :show_all_inline_media => true,
+ :profile_sidebar_fill_color => "DDEEF6",
+ :profile_image_url_https => "https://si0.twimg.com/profile_images/301443535/sven.twitter_normal.jpg"
+ }
+ }
+}
0  vendor/assets/stylesheets/.gitkeep
No changes.
0  vendor/plugins/.gitkeep
No changes.
20 vendor/plugins/country_select/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Michael Koziarski
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 vendor/plugins/country_select/README
@@ -0,0 +1,14 @@
+CountrySelect
+=============
+
+Provides a simple helper to get an HTML select list of countries. The list of countries comes from the ISO 3166 standard. While it is a relatively neutral source of country names, it will still offend some users.
+
+Users are strongly advised to evaluate the suitability of this list given their user base.
+
+Example
+=======
+
+country_select("user", "country_name")
+
+
+Copyright (c) 2008 Michael Koziarski, released under the MIT license
1  vendor/plugins/country_select/init.rb
@@ -0,0 +1 @@
+require 'country_select'
2  vendor/plugins/country_select/install.rb
@@ -0,0 +1,2 @@
+# Install hook code here
+puts "The list of countries provided by this plugin may offend some users. Please review it carefully before you use it"
88 vendor/plugins/country_select/lib/country_select.rb