Created
October 25, 2012 22:14
-
-
Save benmanns/3955802 to your computer and use it in GitHub Desktop.
This diff shows the differences between a fresh Rails installation and RailsApps/rails-stripe-membership-saas as of ea5bc35c02de4be277682dca964bff8dcb84917c.
This file contains hidden or 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
diff --git a/.gitignore b/.gitignore | |
index eb3489a..a6c2a9c 100644 | |
--- a/.gitignore | |
+++ b/.gitignore | |
@@ -1,15 +1,77 @@ | |
+#---------------------------------------------------------------------------- | |
+# Ignore these files when commiting to a git repository. | |
+# | |
# See http://help.github.com/ignore-files/ for more about ignoring files. | |
# | |
+# The original version of this file is found here: | |
+# https://github.com/RailsApps/rails-composer/blob/master/files/gitignore.txt | |
+# | |
+# Corrections? Improvements? Create a GitHub issue: | |
+# http://github.com/RailsApps/rails-composer/issues | |
+#---------------------------------------------------------------------------- | |
+ | |
+# bundler state | |
+/.bundle | |
+/vendor/bundle/ | |
+/vendor/ruby/ | |
+ | |
+# minimal Rails specific artifacts | |
+db/*.sqlite3 | |
+/log/* | |
+/tmp/* | |
+ | |
+# various artifacts | |
+**.war | |
+*.rbc | |
+*.sassc | |
+.rspec | |
+.redcar/ | |
+.sass-cache | |
+/config/config.yml | |
+/config/database.yml | |
+/coverage.data | |
+/coverage/ | |
+/db/*.javadb/ | |
+/db/*.sqlite3 | |
+/doc/api/ | |
+/doc/app/ | |
+/doc/features.html | |
+/doc/specs.html | |
+/public/cache | |
+/public/stylesheets/compiled | |
+/public/system/* | |
+/spec/tmp/* | |
+/cache | |
+/capybara* | |
+/capybara-*.html | |
+/gems | |
+/spec/requests | |
+/spec/routing | |
+/spec/views | |
+/specifications | |
+rerun.txt | |
+pickle-email-*.html | |
+ | |
# If you find yourself ignoring temporary files generated by your text editor | |
# or operating system, you probably want to add a global ignore instead: | |
# git config --global core.excludesfile ~/.gitignore_global | |
+# | |
+# Here are some files you may want to ignore globally: | |
-# Ignore bundler config | |
-/.bundle | |
+# scm revert files | |
+**.orig | |
-# Ignore the default SQLite database. | |
-/db/*.sqlite3 | |
+# Mac finder artifacts | |
+.DS_Store | |
+ | |
+# Netbeans project directory | |
+/nbproject/ | |
+ | |
+# RubyMine project files | |
+.idea | |
+ | |
+# Textmate project files | |
+/*.tmproj | |
-# Ignore all logfiles and tempfiles. | |
-/log/*.log | |
-/tmp | |
+# vim artifacts | |
+**.swp | |
diff --git a/.rvmrc b/.rvmrc | |
new file mode 100644 | |
index 0000000..ab0208a | |
--- /dev/null | |
+++ b/.rvmrc | |
@@ -0,0 +1,48 @@ | |
+#!/usr/bin/env bash | |
+ | |
+# This is an RVM Project .rvmrc file, used to automatically load the ruby | |
+# development environment upon cd'ing into the directory | |
+ | |
+# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional, | |
+# Only full ruby name is supported here, for short names use: | |
+# echo "rvm use 1.9.3" > .rvmrc | |
+environment_id="ruby-1.9.3@rails-stripe-membership-saas" | |
+ | |
+# Uncomment the following lines if you want to verify rvm version per project | |
+# rvmrc_rvm_version="1.14.2 (latest)" # 1.10.1 seams as a safe start | |
+# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || { | |
+# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading." | |
+# return 1 | |
+# } | |
+ | |
+# First we attempt to load the desired environment directly from the environment | |
+# file. This is very fast and efficient compared to running through the entire | |
+# CLI and selector. If you want feedback on which environment was used then | |
+# insert the word 'use' after --create as this triggers verbose mode. | |
+if [[ -d "${rvm_path:-$HOME/.rvm}/environments" | |
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] | |
+then | |
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" | |
+ [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] && | |
+ \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true | |
+else | |
+ # If the environment file has not yet been created, use the RVM CLI to select. | |
+ rvm --create "$environment_id" || { | |
+ echo "Failed to create RVM environment '${environment_id}'." | |
+ return 1 | |
+ } | |
+fi | |
+ | |
+# If you use bundler, this might be useful to you: | |
+# if [[ -s Gemfile ]] && { | |
+# ! builtin command -v bundle >/dev/null || | |
+# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null | |
+# } | |
+# then | |
+# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n" | |
+# gem install bundler | |
+# fi | |
+# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null | |
+# then | |
+# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete' | |
+# fi | |
diff --git a/Gemfile b/Gemfile | |
index 5367a9a..142c56f 100644 | |
--- a/Gemfile | |
+++ b/Gemfile | |
@@ -1,38 +1,24 @@ | |
source 'https://rubygems.org' | |
- | |
gem 'rails', '3.2.8' | |
- | |
-# Bundle edge Rails instead: | |
-# gem 'rails', :git => 'git://github.com/rails/rails.git' | |
- | |
gem 'sqlite3' | |
- | |
- | |
-# Gems used only for assets and not required | |
-# in production environments by default. | |
group :assets do | |
gem 'sass-rails', '~> 3.2.3' | |
gem 'coffee-rails', '~> 3.2.1' | |
- | |
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes | |
- # gem 'therubyracer', :platforms => :ruby | |
- | |
gem 'uglifier', '>= 1.0.3' | |
end | |
- | |
gem 'jquery-rails' | |
- | |
-# To use ActiveModel has_secure_password | |
-# gem 'bcrypt-ruby', '~> 3.0.0' | |
- | |
-# To use Jbuilder templates for JSON | |
-# gem 'jbuilder' | |
- | |
-# Use unicorn as the app server | |
-# gem 'unicorn' | |
- | |
-# Deploy with Capistrano | |
-# gem 'capistrano' | |
- | |
-# To use debugger | |
-# gem 'debugger' | |
+gem "rspec-rails", ">= 2.11.4", :group => [:development, :test] | |
+gem "email_spec", ">= 1.2.1", :group => :test | |
+gem "cucumber-rails", ">= 1.3.0", :group => :test, :require => false | |
+gem "database_cleaner", ">= 0.9.1", :group => :test | |
+gem "launchy", ">= 2.1.2", :group => :test | |
+gem "capybara", ">= 1.1.2", :group => :test | |
+gem "factory_girl_rails", ">= 4.1.0", :group => [:development, :test] | |
+gem "bootstrap-sass", ">= 2.1.0.1" | |
+gem "devise", ">= 2.1.2" | |
+gem "cancan", ">= 1.6.8" | |
+gem "rolify", ">= 3.2.0" | |
+gem "simple_form", ">= 2.0.4" | |
+gem "stripe", ">= 1.7.4" | |
+gem "stripe_event", ">= 0.4.0" | |
+gem "quiet_assets", ">= 1.0.1", :group => :development | |
\ No newline at end of file | |
diff --git a/Gemfile.lock b/Gemfile.lock | |
index 99db546..043cb18 100644 | |
--- a/Gemfile.lock | |
+++ b/Gemfile.lock | |
@@ -28,8 +28,21 @@ GEM | |
activesupport (3.2.8) | |
i18n (~> 0.6) | |
multi_json (~> 1.0) | |
+ addressable (2.3.2) | |
arel (3.0.2) | |
+ bcrypt-ruby (3.0.1) | |
+ bootstrap-sass (2.1.0.1) | |
builder (3.0.4) | |
+ cancan (1.6.8) | |
+ 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.3.6) | |
+ ffi (~> 1.0, >= 1.0.6) | |
coffee-rails (3.2.2) | |
coffee-script (>= 2.2.0) | |
railties (~> 3.2.0) | |
@@ -37,9 +50,36 @@ GEM | |
coffee-script-source | |
execjs | |
coffee-script-source (1.4.0) | |
+ cucumber (1.2.1) | |
+ builder (>= 2.1.2) | |
+ diff-lcs (>= 1.1.3) | |
+ gherkin (~> 2.11.0) | |
+ json (>= 1.4.6) | |
+ cucumber-rails (1.3.0) | |
+ capybara (>= 1.1.2) | |
+ cucumber (>= 1.1.8) | |
+ nokogiri (>= 1.5.0) | |
+ database_cleaner (0.9.1) | |
+ devise (2.1.2) | |
+ bcrypt-ruby (~> 3.0) | |
+ orm_adapter (~> 0.1) | |
+ railties (~> 3.1) | |
+ warden (~> 1.2.1) | |
+ diff-lcs (1.1.3) | |
+ email_spec (1.2.1) | |
+ mail (~> 2.2) | |
+ rspec (~> 2.0) | |
erubis (2.7.0) | |
execjs (1.4.0) | |
multi_json (~> 1.0) | |
+ factory_girl (4.1.0) | |
+ activesupport (>= 3.0.0) | |
+ factory_girl_rails (4.1.0) | |
+ factory_girl (~> 4.1.0) | |
+ railties (>= 3.0.0) | |
+ ffi (1.1.5) | |
+ gherkin (2.11.5) | |
+ json (>= 1.4.6) | |
hike (1.2.1) | |
i18n (0.6.1) | |
journey (1.0.4) | |
@@ -47,13 +87,21 @@ GEM | |
railties (>= 3.1.0, < 5.0) | |
thor (~> 0.14) | |
json (1.7.5) | |
+ launchy (2.1.2) | |
+ addressable (~> 2.3) | |
+ libwebsocket (0.1.5) | |
+ addressable | |
mail (2.4.4) | |
i18n (>= 0.4.0) | |
mime-types (~> 1.16) | |
treetop (~> 1.4.8) | |
mime-types (1.19) | |
multi_json (1.3.6) | |
+ nokogiri (1.5.5) | |
+ orm_adapter (0.4.0) | |
polyglot (0.3.3) | |
+ quiet_assets (1.0.1) | |
+ railties (~> 3.1) | |
rack (1.4.1) | |
rack-cache (1.2) | |
rack (>= 0.4) | |
@@ -79,16 +127,47 @@ GEM | |
rake (0.9.2.2) | |
rdoc (3.12) | |
json (~> 1.4) | |
+ rest-client (1.6.7) | |
+ mime-types (>= 1.16) | |
+ rolify (3.2.0) | |
+ rspec (2.11.0) | |
+ rspec-core (~> 2.11.0) | |
+ rspec-expectations (~> 2.11.0) | |
+ rspec-mocks (~> 2.11.0) | |
+ rspec-core (2.11.1) | |
+ rspec-expectations (2.11.3) | |
+ diff-lcs (~> 1.1.3) | |
+ rspec-mocks (2.11.3) | |
+ rspec-rails (2.11.4) | |
+ actionpack (>= 3.0) | |
+ activesupport (>= 3.0) | |
+ railties (>= 3.0) | |
+ rspec (~> 2.11.0) | |
+ rubyzip (0.9.9) | |
sass (3.2.1) | |
sass-rails (3.2.5) | |
railties (~> 3.2.0) | |
sass (>= 3.1.10) | |
tilt (~> 1.3) | |
+ selenium-webdriver (2.25.0) | |
+ childprocess (>= 0.2.5) | |
+ libwebsocket (~> 0.1.3) | |
+ multi_json (~> 1.0) | |
+ rubyzip | |
+ simple_form (2.0.4) | |
+ actionpack (~> 3.0) | |
+ activemodel (~> 3.0) | |
sprockets (2.1.3) | |
hike (~> 1.2) | |
rack (~> 1.0) | |
tilt (~> 1.1, != 1.3.0) | |
sqlite3 (1.3.6) | |
+ stripe (1.7.4) | |
+ multi_json (~> 1.1) | |
+ rest-client (~> 1.4) | |
+ stripe_event (0.4.0) | |
+ rails (~> 3.1) | |
+ stripe (~> 1.6) | |
thor (0.16.0) | |
tilt (1.3.3) | |
treetop (1.4.11) | |
@@ -98,14 +177,33 @@ GEM | |
uglifier (1.3.0) | |
execjs (>= 0.3.0) | |
multi_json (~> 1.0, >= 1.0.2) | |
+ warden (1.2.1) | |
+ rack (>= 1.0) | |
+ xpath (0.1.4) | |
+ nokogiri (~> 1.3) | |
PLATFORMS | |
ruby | |
DEPENDENCIES | |
+ bootstrap-sass (>= 2.1.0.1) | |
+ cancan (>= 1.6.8) | |
+ capybara (>= 1.1.2) | |
coffee-rails (~> 3.2.1) | |
+ cucumber-rails (>= 1.3.0) | |
+ database_cleaner (>= 0.9.1) | |
+ devise (>= 2.1.2) | |
+ email_spec (>= 1.2.1) | |
+ factory_girl_rails (>= 4.1.0) | |
jquery-rails | |
+ launchy (>= 2.1.2) | |
+ quiet_assets (>= 1.0.1) | |
rails (= 3.2.8) | |
+ rolify (>= 3.2.0) | |
+ rspec-rails (>= 2.11.4) | |
sass-rails (~> 3.2.3) | |
+ simple_form (>= 2.0.4) | |
sqlite3 | |
+ stripe (>= 1.7.4) | |
+ stripe_event (>= 0.4.0) | |
uglifier (>= 1.0.3) | |
diff --git a/README b/README | |
new file mode 100644 | |
index 0000000..364b507 | |
--- /dev/null | |
+++ b/README | |
@@ -0,0 +1,28 @@ | |
+Rails Stripe Membership Saas | |
+======================== | |
+ | |
+You can use this project as a starting point for a Rails web application. It requires Rails 3 and uses Devise for user management and authentication, CanCan for authorization, Twitter Bootstrap for CSS styling, and Stripe for recurring billing. | |
+ | |
+Devise | |
+http://github.com/plataformatec/devise | |
+ | |
+CanCan | |
+https://github.com/ryanb/cancan | |
+ | |
+Twitter Bootstrap | |
+http://twitter.github.com/bootstrap/ | |
+ | |
+Stripe | |
+https://stripe.com/ | |
+ | |
+________________________ | |
+ | |
+For more information, please see the updated README file on GitHub: | |
+ | |
+https://github.com/railsapps/rails-stripe-membership-saas | |
+ | |
+________________________ | |
+ | |
+MIT License | |
+ | |
+http://www.opensource.org/licenses/mit-license | |
\ No newline at end of file | |
diff --git a/README.rdoc b/README.rdoc | |
deleted file mode 100644 | |
index 7c36f23..0000000 | |
--- a/README.rdoc | |
+++ /dev/null | |
@@ -1,261 +0,0 @@ | |
-== Welcome to Rails | |
- | |
-Rails is a web-application framework that includes everything needed to create | |
-database-backed web applications according to the Model-View-Control pattern. | |
- | |
-This pattern splits the view (also called the presentation) into "dumb" | |
-templates that are primarily responsible for inserting pre-built data in between | |
-HTML tags. The model contains the "smart" domain objects (such as Account, | |
-Product, Person, Post) that holds all the business logic and knows how to | |
-persist themselves to a database. The controller handles the incoming requests | |
-(such as Save New Account, Update Product, Show Post) by manipulating the model | |
-and directing data to the view. | |
- | |
-In Rails, the model is handled by what's called an object-relational mapping | |
-layer entitled Active Record. This layer allows you to present the data from | |
-database rows as objects and embellish these data objects with business logic | |
-methods. You can read more about Active Record in | |
-link:files/vendor/rails/activerecord/README.html. | |
- | |
-The controller and view are handled by the Action Pack, which handles both | |
-layers by its two parts: Action View and Action Controller. These two layers | |
-are bundled in a single package due to their heavy interdependence. This is | |
-unlike the relationship between the Active Record and Action Pack that is much | |
-more separate. Each of these packages can be used independently outside of | |
-Rails. You can read more about Action Pack in | |
-link:files/vendor/rails/actionpack/README.html. | |
- | |
- | |
-== Getting Started | |
- | |
-1. At the command prompt, create a new Rails application: | |
- <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name) | |
- | |
-2. Change directory to <tt>myapp</tt> and start the web server: | |
- <tt>cd myapp; rails server</tt> (run with --help for options) | |
- | |
-3. Go to http://localhost:3000/ and you'll see: | |
- "Welcome aboard: You're riding Ruby on Rails!" | |
- | |
-4. Follow the guidelines to start developing your application. You can find | |
-the following resources handy: | |
- | |
-* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html | |
-* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ | |
- | |
- | |
-== Debugging Rails | |
- | |
-Sometimes your application goes wrong. Fortunately there are a lot of tools that | |
-will help you debug it and get it back on the rails. | |
- | |
-First area to check is the application log files. Have "tail -f" commands | |
-running on the server.log and development.log. Rails will automatically display | |
-debugging and runtime information to these files. Debugging info will also be | |
-shown in the browser on requests from 127.0.0.1. | |
- | |
-You can also log your own messages directly into the log file from your code | |
-using the Ruby logger class from inside your controllers. Example: | |
- | |
- class WeblogController < ActionController::Base | |
- def destroy | |
- @weblog = Weblog.find(params[:id]) | |
- @weblog.destroy | |
- logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") | |
- end | |
- end | |
- | |
-The result will be a message in your log file along the lines of: | |
- | |
- Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! | |
- | |
-More information on how to use the logger is at http://www.ruby-doc.org/core/ | |
- | |
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are | |
-several books available online as well: | |
- | |
-* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) | |
-* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) | |
- | |
-These two books will bring you up to speed on the Ruby language and also on | |
-programming in general. | |
- | |
- | |
-== Debugger | |
- | |
-Debugger support is available through the debugger command when you start your | |
-Mongrel or WEBrick server with --debugger. This means that you can break out of | |
-execution at any point in the code, investigate and change the model, and then, | |
-resume execution! You need to install ruby-debug to run the server in debugging | |
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example: | |
- | |
- class WeblogController < ActionController::Base | |
- def index | |
- @posts = Post.all | |
- debugger | |
- end | |
- end | |
- | |
-So the controller will accept the action, run the first line, then present you | |
-with a IRB prompt in the server window. Here you can do things like: | |
- | |
- >> @posts.inspect | |
- => "[#<Post:0x14a6be8 | |
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>, | |
- #<Post:0x14a6620 | |
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]" | |
- >> @posts.first.title = "hello from a debugger" | |
- => "hello from a debugger" | |
- | |
-...and even better, you can examine how your runtime objects actually work: | |
- | |
- >> f = @posts.first | |
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}> | |
- >> f. | |
- Display all 152 possibilities? (y or n) | |
- | |
-Finally, when you're ready to resume execution, you can enter "cont". | |
- | |
- | |
-== Console | |
- | |
-The console is a Ruby shell, which allows you to interact with your | |
-application's domain model. Here you'll have all parts of the application | |
-configured, just like it is when the application is running. You can inspect | |
-domain models, change values, and save to the database. Starting the script | |
-without arguments will launch it in the development environment. | |
- | |
-To start the console, run <tt>rails console</tt> from the application | |
-directory. | |
- | |
-Options: | |
- | |
-* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications | |
- made to the database. | |
-* Passing an environment name as an argument will load the corresponding | |
- environment. Example: <tt>rails console production</tt>. | |
- | |
-To reload your controllers and models after launching the console run | |
-<tt>reload!</tt> | |
- | |
-More information about irb can be found at: | |
-link:http://www.rubycentral.org/pickaxe/irb.html | |
- | |
- | |
-== dbconsole | |
- | |
-You can go to the command line of your database directly through <tt>rails | |
-dbconsole</tt>. You would be connected to the database with the credentials | |
-defined in database.yml. Starting the script without arguments will connect you | |
-to the development database. Passing an argument will connect you to a different | |
-database, like <tt>rails dbconsole production</tt>. Currently works for MySQL, | |
-PostgreSQL and SQLite 3. | |
- | |
-== Description of Contents | |
- | |
-The default directory structure of a generated Ruby on Rails application: | |
- | |
- |-- app | |
- | |-- assets | |
- | |-- images | |
- | |-- javascripts | |
- | `-- stylesheets | |
- | |-- controllers | |
- | |-- helpers | |
- | |-- mailers | |
- | |-- models | |
- | `-- views | |
- | `-- layouts | |
- |-- config | |
- | |-- environments | |
- | |-- initializers | |
- | `-- locales | |
- |-- db | |
- |-- doc | |
- |-- lib | |
- | `-- tasks | |
- |-- log | |
- |-- public | |
- |-- script | |
- |-- test | |
- | |-- fixtures | |
- | |-- functional | |
- | |-- integration | |
- | |-- performance | |
- | `-- unit | |
- |-- tmp | |
- | |-- cache | |
- | |-- pids | |
- | |-- sessions | |
- | `-- sockets | |
- `-- vendor | |
- |-- assets | |
- `-- stylesheets | |
- `-- plugins | |
- | |
-app | |
- Holds all the code that's specific to this particular application. | |
- | |
-app/assets | |
- Contains subdirectories for images, stylesheets, and JavaScript files. | |
- | |
-app/controllers | |
- Holds controllers that should be named like weblogs_controller.rb for | |
- automated URL mapping. All controllers should descend from | |
- ApplicationController which itself descends from ActionController::Base. | |
- | |
-app/models | |
- Holds models that should be named like post.rb. Models descend from | |
- ActiveRecord::Base by default. | |
- | |
-app/views | |
- Holds the template files for the view that should be named like | |
- weblogs/index.html.erb for the WeblogsController#index action. All views use | |
- eRuby syntax by default. | |
- | |
-app/views/layouts | |
- Holds the template files for layouts to be used with views. This models the | |
- common header/footer method of wrapping views. In your views, define a layout | |
- using the <tt>layout :default</tt> and create a file named default.html.erb. | |
- Inside default.html.erb, call <% yield %> to render the view using this | |
- layout. | |
- | |
-app/helpers | |
- Holds view helpers that should be named like weblogs_helper.rb. These are | |
- generated for you automatically when using generators for controllers. | |
- Helpers can be used to wrap functionality for your views into methods. | |
- | |
-config | |
- Configuration files for the Rails environment, the routing map, the database, | |
- and other dependencies. | |
- | |
-db | |
- Contains the database schema in schema.rb. db/migrate contains all the | |
- sequence of Migrations for your schema. | |
- | |
-doc | |
- This directory is where your application documentation will be stored when | |
- generated using <tt>rake doc:app</tt> | |
- | |
-lib | |
- Application specific libraries. Basically, any kind of custom code that | |
- doesn't belong under controllers, models, or helpers. This directory is in | |
- the load path. | |
- | |
-public | |
- The directory available for the web server. Also contains the dispatchers and the | |
- default HTML files. This should be set as the DOCUMENT_ROOT of your web | |
- server. | |
- | |
-script | |
- Helper scripts for automation and generation. | |
- | |
-test | |
- Unit and functional tests along with fixtures. When using the rails generate | |
- command, template test files will be generated for you and placed in this | |
- directory. | |
- | |
-vendor | |
- External libraries that the application depends on. Also includes the plugins | |
- subdirectory. If the app has frozen rails, those gems also go here, under | |
- vendor/rails/. This directory is in the load path. | |
diff --git a/README.textile b/README.textile | |
new file mode 100644 | |
index 0000000..85a9e5e | |
--- /dev/null | |
+++ b/README.textile | |
@@ -0,0 +1,422 @@ | |
+h1. !http://railsapps.github.com/images/rails-36x36.jpg(Rails Application for a Membership, Subscription, or SaaS Site)! Rails App for a Membership, Subscription, or SaaS Site | |
+ | |
+Rails 3.2 example application with recurring billing using Stripe. Use for a Rails membership site, subscription site, or SaaS site (software-as-a-service). Using: | |
+ | |
+* "Devise":http://github.com/plataformatec/devise authentication and user management | |
+* "CanCan":https://github.com/ryanb/cancan authorization for administrator access | |
+* "Twitter Bootstrap":http://twitter.github.com/bootstrap/ front-end framework for CSS styling | |
+* "Stripe":https://stripe.com/ for recurring billing and subscription management | |
+ | |
+Best of all, there's a "detailed tutorial":http://railsapps.github.com/tutorial-rails-stripe-membership-saas.html to show how it's built. | |
+ | |
+!http://railsapps.github.com/images/rails-stripe-membership-saas.png(Rails Application for a Membership, Subscription, or SaaS Site)! | |
+ | |
+h2. !http://twitter-badges.s3.amazonaws.com/t_logo-a.png(Follow on Twitter)!:http://www.twitter.com/rails_apps Follow on Twitter | |
+ | |
+Follow the project on Twitter: "@rails_apps":http://twitter.com/rails_apps. Please tweet some praise if you like what you've found. | |
+ | |
+h2. Tutorial | |
+ | |
+A complete tutorial is available: | |
+ | |
+h4. "View the Tutorial":http://railsapps.github.com/tutorial-rails-stripe-membership-saas.html | |
+ | |
+The tutorial documents each step to follow to create the application. Every step is documented concisely, so a complete beginner can create this application without any additional knowledge. However, no explanation is offered for any of the steps, so if you are a beginner, you’re advised to look for an introduction to Rails elsewhere. See a list of recommended resources for "Rails":http://railsapps.github.com/rails.html. | |
+ | |
+h2. What Is Implemented -- and What Is Not | |
+ | |
+Membership sites restrict access to content such as articles, videos, or user forums. Software-as-a-service (SaaS) sites limit use of web-based software to paid subscribers. The revenue model is the same whether the site provides high-value content or software as a service: A visitor purchases a subscription and gains access to restricted areas of the site. Typically, the subscription is repurchased monthly through a service that provides recurring billing. | |
+ | |
+The example application provides a complete and fully functional membership site. | |
+ | |
+* tiered pricing for multiple subscription plans | |
+* optional "free trial" subscription as well as free accounts using Stripe | |
+* uses Stripe for no local credit card storage | |
+* Stripe accepts credit card payments from customers in any country or currency | |
+* PCI compliance using the Stripe JavaScript library | |
+* Stripe handles recurring billing, retries if payment fails, and cancels subscription if retries fail | |
+* paid subscriptions are created only after a successful credit card transaction | |
+* subscribers can upgrade or downgrade subscription plans | |
+* subscribers can cancel subscription plans | |
+* configurable subscription renewal period (defaults to one month) | |
+* administrator can change subscription plan or delete user | |
+ | |
+h4. What is Not Implemented | |
+ | |
+There are additional features you may want for a SaaS application, such as: | |
+ | |
+* Basecamp-style subdomains (each user gets their own subdomain) | |
+* "multitenancy":http://en.wikipedia.org/wiki/Multitenancy database segmentation | |
+ | |
+These features are not included in this application. See the "rails3-subdomains":https://github.com/RailsApps/rails3-subdomains example application for help with subdomains. For multitenancy, try Brad Robertson's "Apartment":https://github.com/bradrobertson/apartment gem. | |
+ | |
+h2. Similar Examples and Tutorials | |
+ | |
+This is one in a series of Rails example apps and tutorials from the "RailsApps Project":http://railsapps.github.com/. See a list of additional "Rails examples, tutorials, and starter apps":http://railsapps.github.com/rails-examples-tutorials.html. | |
+ | |
+This example application is based on the "rails3-bootstrap-devise-cancan":https://github.com/RailsApps/rails3-bootstrap-devise-cancan starter application. This example application uses ActiveRecord and a SQLite database with RSpec and Cucumber for testing. | |
+ | |
+You might also be interested in the "rails-prelaunch-signup":https://github.com/RailsApps/rails-prelaunch-signup example and tutorial from the RailsApps project. | |
+ | |
+h2. Dependencies | |
+ | |
+Before generating your application, you will need: | |
+ | |
+* The Ruby language (version 1.9.3) | |
+* Rails 3.2 | |
+ | |
+See the article "Installing Rails":http://railsapps.github.com/installing-rails.html for advice about updating Rails and your development environment. | |
+ | |
+h2. Getting the Application | |
+ | |
+You have several options for getting the code. You can _fork_, _clone_, or _generate_. | |
+ | |
+h3. Fork | |
+ | |
+If you'd like to add features (or bug fixes) to improve the example application, you can fork the GitHub repo and "make pull requests":http://help.github.com/send-pull-requests/. Your code contributions are welcome! | |
+ | |
+h3. Clone | |
+ | |
+If you want to copy and customize the app with changes that are only useful for your own project, you can clone the GitHub repo. You'll need to search-and-replace the project name throughout the application. You probably should generate the app instead (see below). To clone: | |
+ | |
+<pre> | |
+$ git clone git://github.com/RailsApps/rails-stripe-membership-saas.git | |
+</pre> | |
+ | |
+You'll need "git":http://git-scm.com/ on your machine. See "Rails and Git":http://railsapps.github.com/rails-git.html. | |
+ | |
+h3. Generate | |
+ | |
+If you want to use the project as a starter app, use the "Rails Composer":http://railsapps.github.com/rails-composer/ tool to generate a new version of the example app. You'll be able to give it your own project name when you generate the app. Generating the application gives you many additional options. | |
+ | |
+To build the example application, run the command: | |
+ | |
+<pre> | |
+$ rails new rails-stripe-membership-saas -m https://raw.github.com/RailsApps/rails-composer/master/composer.rb -T | |
+</pre> | |
+ | |
+Use the @-T@ flag to skip Test::Unit files. | |
+ | |
+The @$@ character indicates a shell prompt; don't include it when you run the command. | |
+ | |
+This creates a new Rails app named @rails-stripe-membership-saas@ on your computer. You can use a different name if you wish. | |
+ | |
+You'll see a prompt: | |
+ | |
+<pre> | |
+question Install an example application? | |
+ 1) I want to build my own application | |
+ 2) rails-stripe-membership-saas | |
+ 3) rails-prelaunch-signup | |
+ 4) rails3-bootstrap-devise-cancan | |
+ 5) rails3-devise-rspec-cucumber | |
+ 6) rails3-mongoid-devise | |
+ 7) rails3-mongoid-omniauth | |
+ 8) rails3-subdomains | |
+</pre> | |
+ | |
+Choose *rails-stripe-membership-saas*. The Rails Composer tool may give you other options (other choices may have been added since these notes were written). | |
+ | |
+The application generator template will ask you for additional preferences: | |
+ | |
+<pre> | |
+ question Web server for development? | |
+ 1) WEBrick (default) | |
+ 2) Thin | |
+ 3) Unicorn | |
+ 4) Puma | |
+ question Web server for production? | |
+ 1) Same as development | |
+ 2) Thin | |
+ 3) Unicorn | |
+ 4) Puma | |
+ question Template engine? | |
+ 1) ERB | |
+ 2) Haml | |
+ 3) Slim | |
+ extras Set a robots.txt file to ban spiders? (y/n) | |
+ extras Create a project-specific rvm gemset and .rvmrc? (y/n) | |
+ extras Create a GitHub repository? (y/n) | |
+</pre> | |
+ | |
+h4. Web Servers | |
+ | |
+Use the default WEBrick server for convenience. If you plan to deploy to Heroku, select "thin" as your production webserver. | |
+ | |
+h4. Template Engine | |
+ | |
+The example application uses the default "ERB" Rails template engine. Optionally, you can use another template engine, such as Haml or Slim. See instructions for "Haml and Rails":http://railsapps.github.com/rails-haml.html. | |
+ | |
+h4. Other Choices | |
+ | |
+Set a robots.txt file to ban spiders if you want to keep your new site out of Google search results. | |
+ | |
+It is a good idea to use "rvm":https://rvm.io/, the Ruby Version Manager, and create a project-specific rvm gemset and .rvmrc file (not available on Windows). See "Installing Rails":http://railsapps.github.com/installing-rails.html. | |
+ | |
+If you choose to create a GitHub repository, the generator will prompt you for a GitHub username and password. | |
+ | |
+h4. Troubleshooting | |
+ | |
+If you get an error "OpenSSL certificate verify failed" or "Gem::RemoteFetcher::FetchError: SSL_connect" see the article "OpenSSL errors and Rails":http://railsapps.github.com/openssl-certificate-verify-failed.html. | |
+ | |
+If you get an error like this: | |
+ | |
+<pre> | |
+Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. | |
+ composer Running 'after bundler' callbacks. | |
+The template [...] could not be loaded. | |
+Error: You have already activated ..., but your Gemfile requires .... | |
+Using bundle exec may solve this. | |
+</pre> | |
+ | |
+It's due to conflicting gem versions. See the article "Rails Error: “You have already activated (…)”":http://railsapps.github.com/rails-error-you-have-already-activated.html. | |
+ | |
+h3. Edit the README | |
+ | |
+If you're storing the app in a GitHub repository, please edit the README files to add a description of the app and your contact info. If you don't change the README, people will think I am the author of your version of the application. | |
+ | |
+h2. Getting Started | |
+ | |
+h3. Stripe Account | |
+ | |
+The application implements recurring billing using "Stripe":https://stripe.com/. Before you start, go to the Stripe website and set up an account. You don’t need a credit card merchant account or payment gateway. There’s no approval process to delay getting started. | |
+ | |
+h3. Install the Required Gems | |
+ | |
+Check the Gemfile to see which gems are used by this application. | |
+ | |
+Run the @bundle install@ command to install the required gems on your computer: | |
+ | |
+<pre> | |
+$ bundle install | |
+</pre> | |
+ | |
+You can check which gems are installed on your computer with: | |
+ | |
+<pre> | |
+$ gem list | |
+</pre> | |
+ | |
+Keep in mind that you have installed these gems locally. When you deploy the app to another server, the same gems (and versions) must be available. | |
+ | |
+I recommend using "rvm":https://rvm.io/, the Ruby Version Manager, to create a project-specific gemset for the application. See the article "Installing Rails":http://railsapps.github.com/installing-rails.html. | |
+ | |
+h3. Configure Email | |
+ | |
+You must configure the application for your email account. See the article "Send Email with Rails":http://railsapps.github.com/rails-send-email.html. | |
+ | |
+h3. Configure Devise | |
+ | |
+You can modify the configuration file for Devise if you want to use something other than the defaults: | |
+ | |
+* *config/initializers/devise.rb* | |
+ | |
+h3. Set Up a Database Seed File | |
+ | |
+You'll want to set up a default user so you can easily log in to test the app. You can modify the file *db/seeds.rb* for your own name, email and password. You can also set a list of roles that correspond to the subscription plans you will offer. | |
+ | |
+<pre> | |
+puts 'CREATING ROLES' | |
+Role.create([ | |
+ { :name => 'admin' }, | |
+ { :name => 'silver' }, | |
+ { :name => 'gold' }, | |
+ { :name => 'platinum' } | |
+], :without_protection => true) | |
+puts 'SETTING UP DEFAULT USER LOGIN' | |
+user = User.create! :name => 'First User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user.add_role :admin | |
+puts 'admin created: ' << user.name | |
+user2 = User.create! :name => 'Silver User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user2.add_role :silver | |
+user3 = User.create! :name => 'Gold User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user3.add_role :gold | |
+user4 = User.create! :name => 'Platinum User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user4.add_role :platinum | |
+puts "added users: #{user2.name}, #{user3.name}, #{user4.name}" | |
+</pre> | |
+ | |
+You can change the roles and name, email, and password as you wish. | |
+ | |
+If you include your private password in the file, be sure to add the filename to your *.gitignore* file so that your password doesn't become available in your public GitHub repository. | |
+ | |
+Note that it's not necessary to personalize the *db/seeds.rb* file before you deploy your app. You can deploy the app with an example user and then use the application's "Edit Account" feature to change name, email address, and password after you log in. Use this feature to log in as an administrator and change the user name and password to your own. | |
+ | |
+The *db/seeds.rb* file is the only place where we create a list of possible roles. There is no other configuration of roles. Any new role can be added to the roles datatable with a statement such @user.add_role :superhero@. | |
+ | |
+h3. Set the Database | |
+ | |
+Prepare the database and add the default user to the database by running the commands: | |
+ | |
+<pre> | |
+$ rake db:migrate | |
+$ rake db:seed | |
+</pre> | |
+ | |
+Set the database for running tests: | |
+ | |
+<pre> | |
+$ rake db:test:prepare | |
+</pre> | |
+ | |
+If you’re not using "rvm":https://rvm.io/, the Ruby Version Manager, you should preface each rake command with @bundle exec@. You don’t need to use @bundle exec@ if you are using rvm version 1.11.0 or newer. | |
+ | |
+h3. Configure the Stripe Initializer | |
+ | |
+The Stripe gem requires an API key to operate. You also need to supply a public key when initiating a transaction. You can find both keys on the Stripe website when you visit your "Stripe account page":https://manage.stripe.com/#account/apikeys. You'll store the API and public key in an initializer file. | |
+ | |
+Here's the file *config/initializers/stripe.rb*: | |
+ | |
+<pre> | |
+Stripe.api_key = ENV["STRIPE_API_KEY"] | |
+STRIPE_PUBLIC_KEY = ENV["STRIPE_PUBLIC_KEY"] | |
+</pre> | |
+ | |
+For security, don't record sensitive information in your application code where it might be exposed publicly on a GitHub repo. Instead, set Unix environment variables in the file that is read when starting an interactive shell (the *~/.bashrc* file for the bash shell). This will keep the password out of your repository. | |
+ | |
+Add this to your *~/.bashrc* file (or the equivalent file for your preferred Unix shell): | |
+ | |
+<pre> | |
+export STRIPE_API_KEY="secret" | |
+export STRIPE_PUBLIC_KEY="secret" | |
+</pre> | |
+ | |
+Replace "secret" with your keys. You can find both keys on your "Stripe account page":https://manage.stripe.com/#account/apikeys. Two sets of keys are available: one for testing, one for live transactions. Use the testing keys on your development machine. When you deploy, use the live keys. If you plan to use Heroku for hosting, make a note for yourself to set up the Heroku environment variables after deployment. | |
+ | |
+Here's how to set Heroku environment variables to provide the same data your application obtains from your local shell environment: | |
+ | |
+<pre> | |
+$ heroku config:add STRIPE_API_KEY=secret STRIPE_PUBLIC_KEY=secret | |
+</pre> | |
+ | |
+You can't set Heroku environment variables now; you'll do that after you deploy. | |
+ | |
+h3. Prepare Your Stripe Account | |
+ | |
+Before you can use the application, you must prepare your Stripe account to recognize the subscription plans you'll offer. | |
+ | |
+This step is necessary if you're going to use Stripe to automatically handle recurring billing. To match the example application, tell Stripe that you have three plans named "Silver", "Gold", and "Platinum" that will be billed monthly at rates of $9, $19, and $29. Once a customer is created and assigned a plan, Stripe will do all the work of notifying the user, initiating monthly billing, and contacting the user when a credit card is declined or expires. | |
+ | |
+Go to the Stripe "plan management page":https://manage.stripe.com/#plans to create a subscription plan. Stripe offers "documentation about creating a plan":https://stripe.com/docs/subscriptions and "additional detail about plans":https://stripe.com/docs/api#plans. | |
+ | |
+Look for the toggle switch "Live/Test" and set it to "Test." Click the button to "Create your first plan." | |
+ | |
+Create three different plans with the following values: | |
+ | |
+|_. ID |_. Name |_. Amount |_. Interval | | |
+| silver | Silver | 9.00 | monthly | | |
+| gold | Gold | 19.00 | monthly | | |
+| platinum | Platinum | 29.00 | monthly | | |
+ | |
+"ID" is a unique string of your choice that is used to identify the plan when subscribing a customer. In our application, each plan should have an ID that corresponds to the roles we've created to manage access. "Name" is displayed on invoices and in the Stripe web interface. "Amount" is the subscription price in US dollars. "Interval" is the billing frequency. Optionally, you can specify a trial period (in days). If you include a trial period, the customer won't be billed for the first time until the trial period ends. If the customer cancels before the trial period is over, she'll never be billed at all. | |
+ | |
+h3. Stripe Webhooks | |
+ | |
+When a credit card expires or a monthly transaction is declined, Stripe will automatically retry a recurring payment after it fails. After a number of attempts (set in your Stripe account settings), Stripe will cancel the subscription. Your application needs to know to deny access for a subscriber with an expired account. Stripe provides webhooks to communicate events to you (for details, see the "Stripe webhooks documentation":https://stripe.com/docs/webhooks). | |
+ | |
+A Stripe webhook is an HTTP request from Stripe's servers to your site, containing JSON data that provides data about the event, plus an event id that can be used to retrive the data from the Stripe server. The example application responds to Stripe webhooks, using an implementation provided by Danny Whalen’s "stripe_event":https://github.com/integrallis/stripe_event gem. The stripe_event gem is a Rails engine that responds to webhook requests at "http://localhost:3000/stripe":http://localhost:3000/stripe (you can change the URL by modifying the engine mounting in the *config/routes.rb* file). | |
+ | |
+The example application only responds to "customer.subscription.deleted" events. You can customize the application to respond to other events (such as sending a thank you email in response to an “invoice.payment_succeeded” event). | |
+ | |
+For webhooks to work, you must visit your Stripe dashboard at "https://manage.stripe.com/#account/webhooks":https://manage.stripe.com/#account/webhooks and add the URL for your application, such as "http://example.com/stripe":http://example.com/stripe. | |
+ | |
+h2. Test the App | |
+ | |
+You can check that your app runs properly by entering the command: | |
+ | |
+@$ rails server@ | |
+ | |
+To see your application in action, open a browser window and navigate to "http://localhost:3000/":http://localhost:3000. | |
+ | |
+Sign in as the first user (the administrator) using: | |
+ | |
+* email: [email protected] | |
+* password: please | |
+ | |
+You'll see a navigation link for Admin. Clicking the link will display a page with a list of users at | |
+"http://localhost:3000/users":http://localhost:3000/users. | |
+ | |
+To sign in as the second user, use | |
+ | |
+* email: [email protected] | |
+* password: please | |
+ | |
+The second user will not see the Admin navigation link and will not be able to access the page at | |
+"http://localhost:3000/users":http://localhost:3000/users. | |
+ | |
+You should be able to create additional users using the fake credit card number _4242424242424242_. You'll see the new users listed when you log in as an administrator. And you'll see the new users listed as customers when you visit your Stripe dashboard. | |
+ | |
+Stop the server with Control-C. | |
+ | |
+If you test the app by starting the web server and then leave the server running while you install new gems, you’ll have to restart the server to see any changes. The same is true for changes to configuration files in the config folder. This can be confusing to new Rails developers because you can change files in the app folders without restarting the server. Stop the server each time after testing and you will avoid this issue. | |
+ | |
+h2. Deploy to Heroku | |
+ | |
+For your convenience, here is a "Tutorial for Rails on Heroku":http://railsapps.github.com/rails-heroku-tutorial.html. Heroku provides low cost, easily configured Rails application hosting. | |
+ | |
+Before you deploy to production, modify the *app/models/user.rb* file to remove the statement @return if email.include?('example.com')@. If you don't do so, your application will allow any visitor to create a free account by using an “example.com” email address. | |
+ | |
+Be sure to set up SSL before you make your application available in production. See the "Heroku documentation on SSL":https://devcenter.heroku.com/articles/ssl. | |
+ | |
+h2. Customizing | |
+ | |
+This application provides a fully functional membership site that's ready to take credit card payments and serve access to restricted content. | |
+ | |
+It uses Stripe for payment processing, allows users to change credit cards or subscription plans, and uses Stripe webhooks to delete users who have expired or declined credit cards. | |
+ | |
+You might consider a few enhancements. For example, you might want the application to respond to a Stripe webhook event when a credit card payment is successful by sending a "paid" invoice. Or you might respond to Stripe webhook events when credit card payments are unsuccessful by sending a friendly note encouraging the user to check for an expired credit card before the subscription is cancelled. | |
+ | |
+If you have suggestions for additional features, please create an "issue":http://github.com/RailsApps/rails-stripe-membership-saas/issues on GitHub. | |
+ | |
+h2. Testing | |
+ | |
+This is an early release of the application and does not contain a full suite of RSpec unit tests and Cucumber scenarios and step definitions. | |
+ | |
+After installing the application, run @rake -T@ to check that rake tasks for RSpec and Cucumber are available. | |
+ | |
+Run @rake spec@ to run all RSpec tests. | |
+ | |
+Run @rake cucumber@ (or more simply, @cucumber@) to run all Cucumber scenarios. | |
+ | |
+Please send the author a message, create an issue, or submit a pull request if you can contribute improved RSpec or Cucumber files. | |
+ | |
+h2. Troubleshooting | |
+ | |
+Problems? Check the "issues":https://github.com/RailsApps/rails-stripe-membership-saas/issues. | |
+ | |
+h2. Documentation | |
+ | |
+The "tutorial":http://railsapps.github.com/tutorial-rails-stripe-membership-saas.html provides additional documentation. | |
+ | |
+h2. Issues | |
+ | |
+Please create a "GitHub issue":http://github.com/RailsApps/rails-stripe-membership-saas/issues if you identify any problems or have suggestions for improvements. | |
+ | |
+h2. Where to Get Help | |
+ | |
+Your best source for help with problems is "Stack Overflow":http://stackoverflow.com/questions/tagged/ruby-on-rails-3. Your issue may have been encountered and addressed by others. | |
+ | |
+You can also try "Rails Hotline":http://www.railshotline.com/, a free telephone hotline for Rails help staffed by volunteers. | |
+ | |
+h2. Contributing | |
+ | |
+If you make improvements to this application, please share with others. | |
+ | |
+Send the author a message, create an "issue":http://github.com/RailsApps/rails-stripe-membership-saas/issues, or fork the project and submit a pull request. | |
+ | |
+If you add functionality to this application, create an alternative implementation, or build an application that is similar, please contact me and I'll add a note to the README so that others can find your work. | |
+ | |
+h2. Credits | |
+ | |
+Daniel Kehoe implemented the application and wrote the tutorial. | |
+ | |
+Is the app useful to you? Follow the project on Twitter: "@rails_apps":http://twitter.com/rails_apps | |
+and tweet some praise. I'd love to know you were helped out by what I've put together. | |
+ | |
+h2. MIT License | |
+ | |
+"MIT License":http://www.opensource.org/licenses/mit-license | |
+ | |
+Copyright © 2012 Daniel Kehoe | |
+ | |
+!https://cruel-carlota.pagodabox.com/bd71696145d79ee78f3884f875bc5d8c(githalytics.com alpha)! | |
diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png | |
deleted file mode 100644 | |
index d5edc04..0000000 | |
Binary files a/app/assets/images/rails.png and /dev/null differ | |
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js | |
index 9097d83..8c515ad 100644 | |
--- a/app/assets/javascripts/application.js | |
+++ b/app/assets/javascripts/application.js | |
@@ -12,4 +12,5 @@ | |
// | |
//= require jquery | |
//= require jquery_ujs | |
+//= require bootstrap | |
//= require_tree . | |
diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee | |
new file mode 100644 | |
index 0000000..7615679 | |
--- /dev/null | |
+++ b/app/assets/javascripts/home.js.coffee | |
@@ -0,0 +1,3 @@ | |
+# Place all the behaviors and hooks related to the matching controller here. | |
+# All this logic will automatically be available in application.js. | |
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ | |
diff --git a/app/assets/javascripts/jquery.externalscript.js b/app/assets/javascripts/jquery.externalscript.js | |
new file mode 100644 | |
index 0000000..de9a17b | |
--- /dev/null | |
+++ b/app/assets/javascripts/jquery.externalscript.js | |
@@ -0,0 +1,8 @@ | |
+jQuery.externalScript = function(url, options) { | |
+ options = $.extend(options || {}, { | |
+ dataType: "script", | |
+ cache: true, | |
+ url: url | |
+ }); | |
+ return jQuery.ajax(options); | |
+}; | |
\ No newline at end of file | |
diff --git a/app/assets/javascripts/jquery.readyselector.js b/app/assets/javascripts/jquery.readyselector.js | |
new file mode 100644 | |
index 0000000..681f81e | |
--- /dev/null | |
+++ b/app/assets/javascripts/jquery.readyselector.js | |
@@ -0,0 +1,17 @@ | |
+(function ($) { | |
+ var ready = $.fn.ready; | |
+ $.fn.ready = function (fn) { | |
+ if (this.context === undefined) { | |
+ // The $().ready(fn) case. | |
+ ready(fn); | |
+ } else if (this.selector) { | |
+ ready($.proxy(function(){ | |
+ $(this.selector, this.context).each(fn); | |
+ }, this)); | |
+ } else { | |
+ ready($.proxy(function(){ | |
+ $(this).each(fn); | |
+ }, this)); | |
+ } | |
+ } | |
+})(jQuery); | |
\ No newline at end of file | |
diff --git a/app/assets/javascripts/registrations.js.erb b/app/assets/javascripts/registrations.js.erb | |
new file mode 100644 | |
index 0000000..8a2be3c | |
--- /dev/null | |
+++ b/app/assets/javascripts/registrations.js.erb | |
@@ -0,0 +1,39 @@ | |
+$('.registrations').ready(function() { | |
+ $.externalScript('https://js.stripe.com/v1/').done(function(script, textStatus) { | |
+ Stripe.setPublishableKey('<%= STRIPE_PUBLIC_KEY %>'); | |
+ var subscription = { | |
+ setupForm: function() { | |
+ return $('.card_form').submit(function() { | |
+ $('input[type=submit]').prop('disabled', true); | |
+ if ($('#card_number').length) { | |
+ subscription.processCard(); | |
+ return false; | |
+ } else { | |
+ return true; | |
+ } | |
+ }); | |
+ }, | |
+ processCard: function() { | |
+ var card; | |
+ card = { | |
+ name: $('#user_name').val(), | |
+ number: $('#card_number').val(), | |
+ cvc: $('#card_code').val(), | |
+ expMonth: $('#card_month').val(), | |
+ expYear: $('#card_year').val() | |
+ }; | |
+ return Stripe.createToken(card, subscription.handleStripeResponse); | |
+ }, | |
+ handleStripeResponse: function(status, response) { | |
+ if (status === 200) { | |
+ $('#user_stripe_token').val(response.id) | |
+ $('.card_form')[0].submit() | |
+ } else { | |
+ $('#stripe_error').text(response.error.message).show(); | |
+ return $('input[type=submit]').prop('disabled', false); | |
+ } | |
+ } | |
+ }; | |
+ return subscription.setupForm(); | |
+ }); | |
+}); | |
\ No newline at end of file | |
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css | |
deleted file mode 100644 | |
index 3192ec8..0000000 | |
--- a/app/assets/stylesheets/application.css | |
+++ /dev/null | |
@@ -1,13 +0,0 @@ | |
-/* | |
- * This is a manifest file that'll be compiled into application.css, which will include all the files | |
- * listed below. | |
- * | |
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, | |
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. | |
- * | |
- * 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 . | |
- */ | |
diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss | |
new file mode 100644 | |
index 0000000..3b5cc66 | |
--- /dev/null | |
+++ b/app/assets/stylesheets/application.css.scss | |
@@ -0,0 +1,13 @@ | |
+/* | |
+ * This is a manifest file that'll be compiled into application.css, which will include all the files | |
+ * listed below. | |
+ * | |
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, | |
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. | |
+ * | |
+ * 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 . | |
+*/ | |
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.scss b/app/assets/stylesheets/bootstrap_and_overrides.css.scss | |
new file mode 100644 | |
index 0000000..fc66ab6 | |
--- /dev/null | |
+++ b/app/assets/stylesheets/bootstrap_and_overrides.css.scss | |
@@ -0,0 +1,3 @@ | |
+@import "bootstrap"; | |
+body { padding-top: 60px; } | |
+@import "bootstrap-responsive"; | |
diff --git a/app/assets/stylesheets/home.css.scss b/app/assets/stylesheets/home.css.scss | |
new file mode 100644 | |
index 0000000..f0ddc68 | |
--- /dev/null | |
+++ b/app/assets/stylesheets/home.css.scss | |
@@ -0,0 +1,3 @@ | |
+// Place all the styles related to the home controller here. | |
+// They will automatically be included in application.css. | |
+// You can use Sass (SCSS) here: http://sass-lang.com/ | |
diff --git a/app/assets/stylesheets/pricing.css.scss b/app/assets/stylesheets/pricing.css.scss | |
new file mode 100644 | |
index 0000000..c4061c1 | |
--- /dev/null | |
+++ b/app/assets/stylesheets/pricing.css.scss | |
@@ -0,0 +1,17 @@ | |
+.plans{ | |
+ text-align: center; | |
+} | |
+.featured{ | |
+ -webkit-transform:scale(1.15); | |
+ box-shadow: 0 0 30px rgba(0, 0, 0, 0.67); | |
+} | |
+.plan{ | |
+ background-color: #111575; | |
+} | |
+.plan.featured-plan{ | |
+ background-color: #CCAB00; | |
+} | |
+.plan h2{ | |
+ line-height: 100px; | |
+ color: #fff; | |
+} | |
\ No newline at end of file | |
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb | |
index e8065d9..eba9f41 100644 | |
--- a/app/controllers/application_controller.rb | |
+++ b/app/controllers/application_controller.rb | |
@@ -1,3 +1,23 @@ | |
class ApplicationController < ActionController::Base | |
protect_from_forgery | |
-end | |
+ | |
+ rescue_from CanCan::AccessDenied do |exception| | |
+ redirect_to root_path, :alert => exception.message | |
+ end | |
+ | |
+ def after_sign_in_path_for(resource) | |
+ case current_user.roles.first.name | |
+ when 'admin' | |
+ users_path | |
+ when 'silver' | |
+ content_silver_path | |
+ when 'gold' | |
+ content_gold_path | |
+ when 'platinum' | |
+ content_platinum_path | |
+ else | |
+ root_path | |
+ end | |
+ end | |
+ | |
+end | |
\ No newline at end of file | |
diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb | |
new file mode 100644 | |
index 0000000..dd7888a | |
--- /dev/null | |
+++ b/app/controllers/content_controller.rb | |
@@ -0,0 +1,15 @@ | |
+class ContentController < ApplicationController | |
+ before_filter :authenticate_user! | |
+ | |
+ def silver | |
+ authorize! :view, :silver, :message => 'Access limited to Silver Plan subscribers.' | |
+ end | |
+ | |
+ def gold | |
+ authorize! :view, :gold, :message => 'Access limited to Gold Plan subscribers.' | |
+ end | |
+ | |
+ def platinum | |
+ authorize! :view, :platinum, :message => 'Access limited to Platinum Plan subscribers.' | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb | |
new file mode 100644 | |
index 0000000..fc0b474 | |
--- /dev/null | |
+++ b/app/controllers/home_controller.rb | |
@@ -0,0 +1,2 @@ | |
+class HomeController < ApplicationController | |
+end | |
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb | |
new file mode 100644 | |
index 0000000..d46db79 | |
--- /dev/null | |
+++ b/app/controllers/registrations_controller.rb | |
@@ -0,0 +1,15 @@ | |
+class RegistrationsController < Devise::RegistrationsController | |
+ | |
+ def new | |
+ @plan = params[:plan] | |
+ super | |
+ end | |
+ | |
+ private | |
+ def build_resource(*args) | |
+ super | |
+ if params[:plan] | |
+ resource.add_role(params[:plan]) | |
+ end | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb | |
new file mode 100644 | |
index 0000000..536002e | |
--- /dev/null | |
+++ b/app/controllers/users_controller.rb | |
@@ -0,0 +1,33 @@ | |
+class UsersController < ApplicationController | |
+ before_filter :authenticate_user! | |
+ | |
+ def index | |
+ authorize! :index, @user, :message => 'Not authorized as an administrator.' | |
+ @users = User.all | |
+ end | |
+ | |
+ def show | |
+ @user = User.find(params[:id]) | |
+ end | |
+ | |
+ def update | |
+ authorize! :update, @user, :message => 'Not authorized as an administrator.' | |
+ @user = User.find(params[:id]) | |
+ if @user.update_attributes(params[:user], :as => :admin) | |
+ redirect_to users_path, :notice => "User updated." | |
+ else | |
+ redirect_to users_path, :alert => "Unable to update user." | |
+ end | |
+ end | |
+ | |
+ def destroy | |
+ authorize! :destroy, @user, :message => 'Not authorized as an administrator.' | |
+ user = User.find(params[:id]) | |
+ unless user == current_user | |
+ user.destroy | |
+ redirect_to users_path, :notice => "User deleted." | |
+ else | |
+ redirect_to users_path, :notice => "Can't delete yourself." | |
+ end | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb | |
new file mode 100644 | |
index 0000000..3199604 | |
--- /dev/null | |
+++ b/app/helpers/content_helper.rb | |
@@ -0,0 +1,2 @@ | |
+module ContentHelper | |
+end | |
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb | |
new file mode 100644 | |
index 0000000..23de56a | |
--- /dev/null | |
+++ b/app/helpers/home_helper.rb | |
@@ -0,0 +1,2 @@ | |
+module HomeHelper | |
+end | |
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb | |
new file mode 100644 | |
index 0000000..a435f8a | |
--- /dev/null | |
+++ b/app/mailers/user_mailer.rb | |
@@ -0,0 +1,7 @@ | |
+class UserMailer < ActionMailer::Base | |
+ default :from => "[email protected]" | |
+ | |
+ def expire_email(user) | |
+ mail(:to => user.email, :subject => "Subscription Cancelled") | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/app/models/ability.rb b/app/models/ability.rb | |
new file mode 100644 | |
index 0000000..5a9ee96 | |
--- /dev/null | |
+++ b/app/models/ability.rb | |
@@ -0,0 +1,14 @@ | |
+class Ability | |
+ include CanCan::Ability | |
+ | |
+ def initialize(user) | |
+ user ||= User.new # guest user (not logged in) | |
+ if user.has_role? :admin | |
+ can :manage, :all | |
+ else | |
+ can :view, :silver if user.has_role? :silver | |
+ can :view, :gold if user.has_role? :gold | |
+ can :view, :platinum if user.has_role? :platinum | |
+ end | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/app/models/role.rb b/app/models/role.rb | |
new file mode 100644 | |
index 0000000..145baa7 | |
--- /dev/null | |
+++ b/app/models/role.rb | |
@@ -0,0 +1,6 @@ | |
+class Role < ActiveRecord::Base | |
+ has_and_belongs_to_many :users, :join_table => :users_roles | |
+ belongs_to :resource, :polymorphic => true | |
+ | |
+ scopify | |
+end | |
diff --git a/app/models/user.rb b/app/models/user.rb | |
new file mode 100644 | |
index 0000000..73ab734 | |
--- /dev/null | |
+++ b/app/models/user.rb | |
@@ -0,0 +1,65 @@ | |
+class User < ActiveRecord::Base | |
+ rolify | |
+ # Include default devise modules. Others available are: | |
+ # :token_authenticatable, :confirmable, | |
+ # :lockable, :timeoutable and :omniauthable | |
+ devise :database_authenticatable, :registerable, | |
+ :recoverable, :rememberable, :trackable, :validatable | |
+ | |
+ # Setup accessible (or protected) attributes for your model | |
+ attr_accessible :role_ids, :as => :admin | |
+ attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :stripe_token | |
+ attr_accessor :stripe_token | |
+ before_save :update_stripe | |
+ before_destroy :cancel_subscription | |
+ | |
+ def update_stripe | |
+ return if email.include?('@example.com') | |
+ if customer_id.nil? | |
+ if !stripe_token.present? | |
+ raise "Stripe token not present. Can't create account." | |
+ end | |
+ customer = Stripe::Customer.create( | |
+ :email => email, | |
+ :description => name, | |
+ :card => stripe_token, | |
+ :plan => roles.first.name | |
+ ) | |
+ else | |
+ customer = Stripe::Customer.retrieve(customer_id) | |
+ if stripe_token.present? | |
+ customer.card = stripe_token | |
+ end | |
+ customer.email = email | |
+ customer.description = name | |
+ customer.save | |
+ end | |
+ self.last_4_digits = customer.active_card.last4 | |
+ self.customer_id = customer.id | |
+ self.stripe_token = nil | |
+ rescue Stripe::StripeError => e | |
+ logger.error e.message | |
+ errors.add :base, "Unable to create your subscription. #{e.message}" | |
+ stripe_token = nil | |
+ false | |
+ end | |
+ | |
+ def cancel_subscription | |
+ unless customer_id.nil? | |
+ customer = Stripe::Customer.retrieve(customer_id) | |
+ if (!customer.nil?) && (customer.subscription.status == 'active') | |
+ customer.cancel_subscription | |
+ end | |
+ end | |
+ rescue Stripe::StripeError => e | |
+ logger.error e.message | |
+ errors.add :base, "Unable to cancel your subscription. #{e.message}" | |
+ false | |
+ end | |
+ | |
+ def expire | |
+ UserMailer.expire_email(self).deliver | |
+ destroy | |
+ end | |
+ | |
+end | |
\ No newline at end of file | |
diff --git a/app/views/content/gold.html.erb b/app/views/content/gold.html.erb | |
new file mode 100644 | |
index 0000000..857dd14 | |
--- /dev/null | |
+++ b/app/views/content/gold.html.erb | |
@@ -0,0 +1,2 @@ | |
+<h1>Content#gold</h1> | |
+<p>Find me in app/views/content/gold.html.erb</p> | |
diff --git a/app/views/content/platinum.html.erb b/app/views/content/platinum.html.erb | |
new file mode 100644 | |
index 0000000..c03a51a | |
--- /dev/null | |
+++ b/app/views/content/platinum.html.erb | |
@@ -0,0 +1,2 @@ | |
+<h1>Content#platinum</h1> | |
+<p>Find me in app/views/content/platinum.html.erb</p> | |
diff --git a/app/views/content/silver.html.erb b/app/views/content/silver.html.erb | |
new file mode 100644 | |
index 0000000..8ab27d0 | |
--- /dev/null | |
+++ b/app/views/content/silver.html.erb | |
@@ -0,0 +1,2 @@ | |
+<h1>Content#silver</h1> | |
+<p>Find me in app/views/content/silver.html.erb</p> | |
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb | |
new file mode 100644 | |
index 0000000..4842f72 | |
--- /dev/null | |
+++ b/app/views/devise/registrations/edit.html.erb | |
@@ -0,0 +1,78 @@ | |
+<h2>Account</h2> | |
+ | |
+<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-vertical' }) do |f| %> | |
+ <%= f.error_notification %> | |
+ <%= f.input :name, :autofocus => true %> | |
+ <%= f.input :email, :required => true %> | |
+ <%= f.input :password, :autocomplete => "off", :hint => "leave it blank if you don't want to change it", :required => false %> | |
+ <%= f.input :password_confirmation, :required => false %> | |
+ <%= f.input :current_password, :hint => "we need your current password to confirm your changes", :required => true %> | |
+ <%= f.button :submit, 'Update', :class => 'btn-primary' %> | |
+<% end %> | |
+ | |
+<h3>Subscription Plan</h3> | |
+<p> | |
+ <%= @user.roles.first.name.titleize %> | |
+ <a data-toggle="modal" href="#plan-options" class="btn btn-mini" type="button">Change plan</a> | |
+</p> | |
+ | |
+<h3>Card</h3> | |
+<p> | |
+ Using card ending with <%= @user.last_4_digits %> | |
+ <a data-toggle="modal" href="#card-data" class="btn btn-mini" type="button">Change card</a> | |
+</p> | |
+ | |
+<h3>Cancel my account</h3> | |
+ | |
+<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete, :class => 'btn btn-mini' %></p> | |
+ | |
+<div id="plan-options" class="modal" style="display: none;"> | |
+ <%= simple_form_for resource, :as => resource_name, :url => registration_path(resource_name) , :html => {:method => :put, :class => 'form-horizontal' } do |f| %> | |
+ <div class="modal-header"> | |
+ <a class="close" data-dismiss="modal">×</a> | |
+ <h3>Change Plan</h3> | |
+ </div> | |
+ <div class="modal-body"> | |
+ <%= f.input :role_ids, :collection => Role.all.delete_if {|i| i.name == 'admin'}, :as => :radio_buttons, :label_method => lambda {|t| t.name.titleize}, :label => false, :item_wrapper_class => 'inline' %> | |
+ <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> | |
+ <%= f.password_field :current_password %></div> | |
+ </div> | |
+ <div class="modal-footer"> | |
+ <%= f.submit "Change Plan", :class => "btn.btn-success" %> | |
+ <a class="btn" data-dismiss="modal" href="#">Close</a> | |
+ </div> | |
+ <% end %> | |
+</div> | |
+ | |
+<div id="card-data" class="modal" style="display: none;"> | |
+ <%= simple_form_for resource, :as => resource_name, :url => registration_path(resource_name) , :html => {:method => :put, :class => 'form-horizontal card_form' } do |f| %> | |
+ <div class="modal-header"> | |
+ <a class="close" data-dismiss="modal">×</a> | |
+ <h3>Change Credit Card</h3> | |
+ </div> | |
+ <div class="modal-body"> | |
+ <div class="field"> | |
+ <%= label_tag :card_number, "Credit Card Number" %> | |
+ <%= text_field_tag :card_number, nil, name: nil %> | |
+ </div> | |
+ <div class="field"> | |
+ <%= label_tag :card_code, "Card Security Code (CVV)" %> | |
+ <%= text_field_tag :card_code, nil, name: nil %> | |
+ </div> | |
+ <div class="field"> | |
+ <%= label_tag :card_month, "Card Expiration" %> | |
+ <%= select_month nil, {add_month_numbers_true: true}, {name: nil, id: "card_month"}%> | |
+ <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+10}, {name: nil, id: "card_year"}%> | |
+ </div> | |
+ <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> | |
+ <%= f.password_field :current_password %></div> | |
+ <%= f.hidden_field :name %> | |
+ <%= f.hidden_field :email %> | |
+ <%= f.hidden_field :stripe_token %> | |
+ </div> | |
+ <div class="modal-footer"> | |
+ <%= f.submit "Change Credit Card", :class => "btn.btn-success" %> | |
+ <a class="btn" data-dismiss="modal" href="#">Close</a> | |
+ </div> | |
+ <% end %> | |
+</div> | |
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb | |
new file mode 100644 | |
index 0000000..f477566 | |
--- /dev/null | |
+++ b/app/views/devise/registrations/new.html.erb | |
@@ -0,0 +1,31 @@ | |
+<h2>Sign up</h2> | |
+<div id="stripe_error" class="alert alert-error" style="display:none" > | |
+</div> | |
+<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => 'card_form form-vertical' }) do |f| %> | |
+ <h3><%= @plan.titleize if @plan %> Subscription Plan</h3> | |
+ <%= hidden_field_tag 'plan', @plan %> | |
+ <%= f.error_notification %> | |
+ <%= f.input :name, :autofocus => true %> | |
+ <%= f.input :email, :required => true %> | |
+ <%= f.input :password, :required => true %> | |
+ <%= f.input :password_confirmation, :required => true %> | |
+ <% if @user.stripe_token %> | |
+ Credit card has been accepted. | |
+ <% else %> | |
+ <div class="field"> | |
+ <%= label_tag :card_number, "Credit Card Number" %> | |
+ <%= text_field_tag :card_number, nil, name: nil %> | |
+ </div> | |
+ <div class="field"> | |
+ <%= label_tag :card_code, "Card Security Code (CVV)" %> | |
+ <%= text_field_tag :card_code, nil, name: nil %> | |
+ </div> | |
+ <div class="field"> | |
+ <%= label_tag :card_month, "Card Expiration" %> | |
+ <%= select_month nil, {add_month_numbers_true: true}, {name: nil, id: "card_month"}%> | |
+ <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+10}, {name: nil, id: "card_year"}%> | |
+ </div> | |
+ <% end %> | |
+ <%= f.hidden_field :stripe_token %> | |
+ <%= f.button :submit, 'Sign up', :class => 'btn-primary' %> | |
+<% end %> | |
\ No newline at end of file | |
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb | |
new file mode 100644 | |
index 0000000..8d84d21 | |
--- /dev/null | |
+++ b/app/views/devise/sessions/new.html.erb | |
@@ -0,0 +1,8 @@ | |
+<h2>Sign in</h2> | |
+<%= simple_form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => {:class => 'form-vertical' }) do |f| %> | |
+ <%= f.input :email, :autofocus => true %> | |
+ <%= f.input :password %> | |
+ <%= f.input :remember_me, :as => :boolean if devise_mapping.rememberable? %> | |
+ <%= f.button :submit, "Sign in", :class => 'btn-primary' %> | |
+<% end %> | |
+<%= render "devise/shared/links" %> | |
\ No newline at end of file | |
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb | |
new file mode 100644 | |
index 0000000..eab783a | |
--- /dev/null | |
+++ b/app/views/devise/shared/_links.html.erb | |
@@ -0,0 +1,25 @@ | |
+<%- if controller_name != 'sessions' %> | |
+ <%= link_to "Sign in", new_session_path(resource_name) %><br /> | |
+<% end -%> | |
+ | |
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %> | |
+ <%= link_to "Sign up", new_registration_path(resource_name) %><br /> | |
+<% end -%> | |
+ | |
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' %> | |
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %><br /> | |
+<% end -%> | |
+ | |
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> | |
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br /> | |
+<% end -%> | |
+ | |
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> | |
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br /> | |
+<% end -%> | |
+ | |
+<%- if devise_mapping.omniauthable? %> | |
+ <%- resource_class.omniauth_providers.each do |provider| %> | |
+ <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %><br /> | |
+ <% end -%> | |
+<% end -%> | |
\ No newline at end of file | |
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb | |
new file mode 100644 | |
index 0000000..b6cbd9b | |
--- /dev/null | |
+++ b/app/views/home/index.html.erb | |
@@ -0,0 +1,30 @@ | |
+<div id="welcome" class="hero-unit span7"> | |
+ <h1>Membership Site</h1> | |
+ <h3>Learn to build a successful subscription site.</h3> | |
+</div> | |
+<div class="row span8 plans"> | |
+ <div class="span2 well"> | |
+ <div class="plan"><h2>Silver</h2></div> | |
+ <ul class="unstyled"> | |
+ <li>One lesson a month</li> | |
+ </ul> | |
+ <h3>$9/month</h3> | |
+ <%= link_to 'Subscribe', new_user_registration_path(:plan => 'silver'), :class => 'btn btn-primary' %> | |
+ </div> | |
+ <div class="span2 well featured"> | |
+ <div class="plan featured-plan"><h2>Gold</h2></div> | |
+ <ul class="unstyled"> | |
+ <li>Ten lessons a month</li> | |
+ </ul> | |
+ <h3>$19/month</h3> | |
+ <%= link_to 'Subscribe', new_user_registration_path(:plan => 'gold'), :class => 'btn btn-primary' %> | |
+ </div> | |
+ <div class="span2 well"> | |
+ <div class="plan"><h2>Platinum</h2></div> | |
+ <ul class="unstyled"> | |
+ <li>Thirty lessons a month</li> | |
+ </ul> | |
+ <h3>$29/month</h3> | |
+ <%= link_to 'Subscribe', new_user_registration_path(:plan => 'platinum'), :class => 'btn btn-primary' %> | |
+ </div> | |
+</div> | |
\ No newline at end of file | |
diff --git a/app/views/layouts/_messages.html.erb b/app/views/layouts/_messages.html.erb | |
new file mode 100644 | |
index 0000000..d7df65f | |
--- /dev/null | |
+++ b/app/views/layouts/_messages.html.erb | |
@@ -0,0 +1,8 @@ | |
+<% flash.each do |name, msg| %> | |
+ <% if msg.is_a?(String) %> | |
+ <div class="alert alert-<%= name == :notice ? "success" : "error" %>"> | |
+ <a class="close" data-dismiss="alert">×</a> | |
+ <%= content_tag :div, msg, :id => "flash_#{name}" %> | |
+ </div> | |
+ <% end %> | |
+<% end %> | |
\ No newline at end of file | |
diff --git a/app/views/layouts/_navigation.html.erb b/app/views/layouts/_navigation.html.erb | |
new file mode 100644 | |
index 0000000..6f6e1eb | |
--- /dev/null | |
+++ b/app/views/layouts/_navigation.html.erb | |
@@ -0,0 +1,22 @@ | |
+<%= link_to "Rails Stripe Membership Saas", root_path, :class => 'brand' %> | |
+<ul class="nav"> | |
+ <% if user_signed_in? %> | |
+ <li> | |
+ <%= link_to 'Logout', destroy_user_session_path, :method=>'delete' %> | |
+ </li> | |
+ <% else %> | |
+ <li> | |
+ <%= link_to 'Login', new_user_session_path %> | |
+ </li> | |
+ <% end %> | |
+ <% if user_signed_in? %> | |
+ <li> | |
+ <%= link_to 'Edit account', edit_user_registration_path %> | |
+ </li> | |
+ <% if current_user.has_role? :admin %> | |
+ <li> | |
+ <%= link_to 'Admin', users_path %> | |
+ </li> | |
+ <% end %> | |
+ <% end %> | |
+</ul> | |
\ No newline at end of file | |
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb | |
index 3f847dd..c54dea2 100644 | |
--- a/app/views/layouts/application.html.erb | |
+++ b/app/views/layouts/application.html.erb | |
@@ -1,14 +1,35 @@ | |
<!DOCTYPE html> | |
<html> | |
-<head> | |
- <title>RailsStripeMembershipSaas</title> | |
- <%= stylesheet_link_tag "application", :media => "all" %> | |
- <%= javascript_include_tag "application" %> | |
- <%= csrf_meta_tags %> | |
-</head> | |
-<body> | |
- | |
-<%= yield %> | |
- | |
-</body> | |
-</html> | |
+ <head> | |
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
+ <title><%= content_for?(:title) ? yield(:title) : "Rails Stripe Membership Saas" %></title> | |
+ <meta name="description" content="<%= content_for?(:description) ? yield(:description) : "Rails Stripe Membership Saas" %>"> | |
+ <%= stylesheet_link_tag "application", :media => "all" %> | |
+ <%= javascript_include_tag "application" %> | |
+ <%= csrf_meta_tags %> | |
+ <%= yield(:head) %> | |
+ </head> | |
+ <body class="<%= controller_name %> <%= action_name %>"> | |
+ <header class="navbar navbar-fixed-top"> | |
+ <nav class="navbar-inner"> | |
+ <div class="container"> | |
+ <%= render 'layouts/navigation' %> | |
+ </div> | |
+ </nav> | |
+ </header> | |
+ <div id="main" role="main"> | |
+ <div class="container"> | |
+ <div class="content"> | |
+ <div class="row"> | |
+ <div class="span12"> | |
+ <%= render 'layouts/messages' %> | |
+ <%= yield %> | |
+ </div> | |
+ </div> | |
+ <footer> | |
+ </footer> | |
+ </div> | |
+ </div> <!--! end of .container --> | |
+ </div> <!--! end of #main --> | |
+ </body> | |
+</html> | |
\ No newline at end of file | |
diff --git a/app/views/user_mailer/expire_email.html.erb b/app/views/user_mailer/expire_email.html.erb | |
new file mode 100644 | |
index 0000000..6d72e70 | |
--- /dev/null | |
+++ b/app/views/user_mailer/expire_email.html.erb | |
@@ -0,0 +1,16 @@ | |
+<!DOCTYPE html> | |
+<html> | |
+ <head> | |
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> | |
+ </head> | |
+ <body> | |
+ <h1>Subscription Cancelled</h1> | |
+ <p> | |
+ Your subscription has been cancelled. | |
+ </p> | |
+ <p> | |
+ We are sorry to see you go. We'd love to have you back. | |
+ Visit example.com anytime to create a new subscription. | |
+ </p> | |
+ </body> | |
+</html> | |
\ No newline at end of file | |
diff --git a/app/views/user_mailer/expire_email.text.erb b/app/views/user_mailer/expire_email.text.erb | |
new file mode 100644 | |
index 0000000..f50a9c6 | |
--- /dev/null | |
+++ b/app/views/user_mailer/expire_email.text.erb | |
@@ -0,0 +1,6 @@ | |
+Subscription Cancelled | |
+ | |
+Your subscription has been cancelled. | |
+ | |
+We are sorry to see you go. We'd love to have you back. | |
+Visit example.com anytime to create a new subscription. | |
\ No newline at end of file | |
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb | |
new file mode 100644 | |
index 0000000..1dd25a6 | |
--- /dev/null | |
+++ b/app/views/users/_user.html.erb | |
@@ -0,0 +1,15 @@ | |
+<div id="role-options-<%= user.id %>" class="modal" style="display: none;"> | |
+ <%= simple_form_for user, :url => user_path(user), :html => {:method => :put, :class => 'form-horizontal' } do |f| %> | |
+ <div class="modal-header"> | |
+ <a class="close" data-dismiss="modal">×</a> | |
+ <h3>Change Role</h3> | |
+ </div> | |
+ <div class="modal-body"> | |
+ <%= f.input :role_ids, :collection => Role.all, :as => :radio_buttons, :label_method => lambda {|t| t.name.titleize}, :label => false, :item_wrapper_class => 'inline' %> | |
+ </div> | |
+ <div class="modal-footer"> | |
+ <%= f.submit "Change Role", :class => "btn" %> | |
+ <a class="btn" data-dismiss="modal" href="#">Close</a> | |
+ </div> | |
+ <% end %> | |
+</div> | |
\ No newline at end of file | |
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb | |
new file mode 100644 | |
index 0000000..eeec277 | |
--- /dev/null | |
+++ b/app/views/users/index.html.erb | |
@@ -0,0 +1,30 @@ | |
+<h3>Users</h3> | |
+<div class="span8"> | |
+<table class="table table-condensed"> | |
+ <thead> | |
+ <tr> | |
+ <th>Username</th> | |
+ <th>Email</th> | |
+ <th>Registered</th> | |
+ <th>Role</th> | |
+ <th></th> | |
+ <th></th> | |
+ </tr> | |
+ </thead> | |
+ <tbody> | |
+ <% @users.each do |user| %> | |
+ <tr> | |
+ <td><%= link_to user.name, user %></td> | |
+ <td><%= user.email %></td> | |
+ <td><%= user.created_at.to_date %></td> | |
+ <td><%= user.roles.first.name.titleize unless user.roles.first.nil? %></td> | |
+ <td> | |
+ <a data-toggle="modal" href="#role-options-<%= user.id %>" class="btn btn-mini" type="button">Change role</a> | |
+ <%= render user %> | |
+ </td> | |
+ <td><%= link_to("Delete user", user_path(user), :data => { :confirm => "Are you sure?" }, :method => :delete, :class => 'btn btn-mini') unless user == current_user %></td> | |
+ </tr> | |
+ <% end %> | |
+ </tbody> | |
+</table> | |
+</div> | |
\ No newline at end of file | |
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb | |
new file mode 100644 | |
index 0000000..8b02476 | |
--- /dev/null | |
+++ b/app/views/users/show.html.erb | |
@@ -0,0 +1,3 @@ | |
+<h3>User</h3> | |
+<p>User: <%= @user.name %></p> | |
+<p>Email: <%= @user.email if @user.email %></p> | |
\ No newline at end of file | |
diff --git a/config/application.rb b/config/application.rb | |
index 364ae3d..7fd391b 100644 | |
--- a/config/application.rb | |
+++ b/config/application.rb | |
@@ -1,6 +1,12 @@ | |
require File.expand_path('../boot', __FILE__) | |
-require 'rails/all' | |
+# Pick the frameworks you want: | |
+require "active_record/railtie" | |
+require "action_controller/railtie" | |
+require "action_mailer/railtie" | |
+require "active_resource/railtie" | |
+require "sprockets/railtie" | |
+# require "rails/test_unit/railtie" | |
if defined?(Bundler) | |
# If you precompile assets before deploying to production, use this line | |
@@ -11,12 +17,26 @@ end | |
module RailsStripeMembershipSaas | |
class Application < Rails::Application | |
+ | |
+ # don't generate RSpec tests for views and helpers | |
+ config.generators do |g| | |
+ | |
+ g.test_framework :rspec, fixture: true | |
+ g.fixture_replacement :factory_girl | |
+ | |
+ | |
+ g.view_specs false | |
+ g.helper_specs false | |
+ end | |
+ | |
# 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) | |
+ config.autoload_paths += %W(#{config.root}/lib) | |
+ | |
# 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. | |
@@ -37,7 +57,7 @@ module RailsStripeMembershipSaas | |
config.encoding = "utf-8" | |
# Configure sensitive parameters which will be filtered from the log file. | |
- config.filter_parameters += [:password] | |
+ config.filter_parameters += [:password, :password_confirmation] | |
# Enable escaping HTML in JSON. | |
config.active_support.escape_html_entities_in_json = true | |
diff --git a/config/cucumber.yml b/config/cucumber.yml | |
new file mode 100644 | |
index 0000000..2f0d8ce | |
--- /dev/null | |
+++ b/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 = "-r features/support/ -r features/step_definitions --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 | |
diff --git a/config/database.yml b/config/database.yml | |
index 51a4dd4..09cc85a 100644 | |
--- a/config/database.yml | |
+++ b/config/database.yml | |
@@ -12,7 +12,7 @@ development: | |
# Warning: The database defined as "test" will be erased and | |
# re-generated from your development database when you run "rake". | |
# Do not set this db to the same as development or production. | |
-test: | |
+test: &test | |
adapter: sqlite3 | |
database: db/test.sqlite3 | |
pool: 5 | |
@@ -23,3 +23,6 @@ production: | |
database: db/production.sqlite3 | |
pool: 5 | |
timeout: 5000 | |
+ | |
+cucumber: | |
+ <<: *test | |
\ No newline at end of file | |
diff --git a/config/environments/development.rb b/config/environments/development.rb | |
index 94cad6d..b7e328d 100644 | |
--- a/config/environments/development.rb | |
+++ b/config/environments/development.rb | |
@@ -13,8 +13,25 @@ RailsStripeMembershipSaas::Application.configure do | |
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 | |
+ # ActionMailer Config | |
+ config.action_mailer.default_url_options = { :host => 'localhost:3000' } | |
+ config.action_mailer.delivery_method = :smtp | |
+ # change to true to allow email to be sent during development | |
+ config.action_mailer.perform_deliveries = false | |
+ config.action_mailer.raise_delivery_errors = true | |
+ config.action_mailer.default :charset => "utf-8" | |
+ | |
+ config.action_mailer.smtp_settings = { | |
+ address: "smtp.gmail.com", | |
+ port: 587, | |
+ domain: "example.com", | |
+ authentication: "plain", | |
+ enable_starttls_auto: true, | |
+ user_name: ENV["GMAIL_USERNAME"], | |
+ password: ENV["GMAIL_PASSWORD"] | |
+ } | |
+ | |
+ | |
# Print deprecation notices to the Rails logger | |
config.active_support.deprecation = :log | |
diff --git a/config/environments/production.rb b/config/environments/production.rb | |
index 6ea0a77..e8dbf82 100644 | |
--- a/config/environments/production.rb | |
+++ b/config/environments/production.rb | |
@@ -61,6 +61,26 @@ RailsStripeMembershipSaas::Application.configure do | |
# Send deprecation notices to registered listeners | |
config.active_support.deprecation = :notify | |
+ config.action_mailer.default_url_options = { :host => 'example.com' } | |
+ # ActionMailer Config | |
+ # Setup for production - deliveries, no errors raised | |
+ config.action_mailer.delivery_method = :smtp | |
+ config.action_mailer.perform_deliveries = true | |
+ config.action_mailer.raise_delivery_errors = false | |
+ config.action_mailer.default :charset => "utf-8" | |
+ | |
+ config.action_mailer.smtp_settings = { | |
+ address: "smtp.gmail.com", | |
+ port: 587, | |
+ domain: "example.com", | |
+ authentication: "plain", | |
+ enable_starttls_auto: true, | |
+ user_name: ENV["GMAIL_USERNAME"], | |
+ password: ENV["GMAIL_PASSWORD"] | |
+ } | |
+ | |
+ | |
+ | |
# Log the query plan for queries taking more than this (works | |
# with SQLite, MySQL, and PostgreSQL) | |
# config.active_record.auto_explain_threshold_in_seconds = 0.5 | |
diff --git a/config/environments/test.rb b/config/environments/test.rb | |
index 274a082..871b8fc 100644 | |
--- a/config/environments/test.rb | |
+++ b/config/environments/test.rb | |
@@ -34,4 +34,8 @@ RailsStripeMembershipSaas::Application.configure do | |
# Print deprecation notices to the stderr | |
config.active_support.deprecation = :stderr | |
+ | |
+ # ActionMailer Config | |
+ config.action_mailer.default_url_options = { :host => 'example.com' } | |
+ | |
end | |
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb | |
new file mode 100644 | |
index 0000000..4af1e30 | |
--- /dev/null | |
+++ b/config/initializers/devise.rb | |
@@ -0,0 +1,232 @@ | |
+# Use this hook to configure devise mailer, warden hooks and so forth. | |
+# Many of these configuration options can be set straight in your model. | |
+Devise.setup do |config| | |
+ # ==> Mailer Configuration | |
+ # Configure the e-mail address which will be shown in Devise::Mailer, | |
+ # note that it will be overwritten if you use your own mailer class with default "from" parameter. | |
+ config.mailer_sender = "[email protected]" | |
+ | |
+ # Configure the class responsible to send e-mails. | |
+ # config.mailer = "Devise::Mailer" | |
+ | |
+ # ==> ORM configuration | |
+ # Load and configure the ORM. Supports :active_record (default) and | |
+ # :mongoid (bson_ext recommended) by default. Other ORMs may be | |
+ # available as additional gems. | |
+ require 'devise/orm/active_record' | |
+ | |
+ # ==> Configuration for any authentication mechanism | |
+ # Configure which keys are used when authenticating a user. The default is | |
+ # just :email. You can configure it to use [:username, :subdomain], so for | |
+ # authenticating a user, both parameters are required. Remember that those | |
+ # parameters are used only when authenticating and not when retrieving from | |
+ # session. If you need permissions, you should implement that in a before filter. | |
+ # You can also supply a hash where the value is a boolean determining whether | |
+ # or not authentication should be aborted when the value is not present. | |
+ # config.authentication_keys = [ :email ] | |
+ | |
+ # Configure parameters from the request object used for authentication. Each entry | |
+ # given should be a request method and it will automatically be passed to the | |
+ # find_for_authentication method and considered in your model lookup. For instance, | |
+ # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. | |
+ # The same considerations mentioned for authentication_keys also apply to request_keys. | |
+ # config.request_keys = [] | |
+ | |
+ # Configure which authentication keys should be case-insensitive. | |
+ # These keys will be downcased upon creating or modifying a user and when used | |
+ # to authenticate or find a user. Default is :email. | |
+ config.case_insensitive_keys = [ :email ] | |
+ | |
+ # Configure which authentication keys should have whitespace stripped. | |
+ # These keys will have whitespace before and after removed upon creating or | |
+ # modifying a user and when used to authenticate or find a user. Default is :email. | |
+ config.strip_whitespace_keys = [ :email ] | |
+ | |
+ # Tell if authentication through request.params is enabled. True by default. | |
+ # It can be set to an array that will enable params authentication only for the | |
+ # given strategies, for example, `config.params_authenticatable = [:database]` will | |
+ # enable it only for database (email + password) authentication. | |
+ # config.params_authenticatable = true | |
+ | |
+ # Tell if authentication through HTTP Basic Auth is enabled. False by default. | |
+ # It can be set to an array that will enable http authentication only for the | |
+ # given strategies, for example, `config.http_authenticatable = [:token]` will | |
+ # enable it only for token authentication. | |
+ # config.http_authenticatable = false | |
+ | |
+ # If http headers should be returned for AJAX requests. True by default. | |
+ # config.http_authenticatable_on_xhr = true | |
+ | |
+ # The realm used in Http Basic Authentication. "Application" by default. | |
+ # config.http_authentication_realm = "Application" | |
+ | |
+ # It will change confirmation, password recovery and other workflows | |
+ # to behave the same regardless if the e-mail provided was right or wrong. | |
+ # Does not affect registerable. | |
+ # config.paranoid = true | |
+ | |
+ # By default Devise will store the user in session. You can skip storage for | |
+ # :http_auth and :token_auth by adding those symbols to the array below. | |
+ # Notice that if you are skipping storage for all authentication paths, you | |
+ # may want to disable generating routes to Devise's sessions controller by | |
+ # passing :skip => :sessions to `devise_for` in your config/routes.rb | |
+ config.skip_session_storage = [:http_auth] | |
+ | |
+ # ==> Configuration for :database_authenticatable | |
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If | |
+ # using other encryptors, it sets how many times you want the password re-encrypted. | |
+ # | |
+ # Limiting the stretches to just one in testing will increase the performance of | |
+ # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use | |
+ # a value less than 10 in other environments. | |
+ config.stretches = Rails.env.test? ? 1 : 10 | |
+ | |
+ # Setup a pepper to generate the encrypted password. | |
+ # config.pepper = "ea88ee07de2500c87684d1a819ce5757ed0131fe24f07d97075caf9c5b301ad6dfd3ccf780dda4964e5ea977dd5e50a91f77b3c228608015c02127c50ce98484" | |
+ | |
+ # ==> Configuration for :confirmable | |
+ # A period that the user is allowed to access the website even without | |
+ # confirming his account. For instance, if set to 2.days, the user will be | |
+ # able to access the website for two days without confirming his account, | |
+ # access will be blocked just in the third day. Default is 0.days, meaning | |
+ # the user cannot access the website without confirming his account. | |
+ # config.allow_unconfirmed_access_for = 2.days | |
+ | |
+ # If true, requires any email changes to be confirmed (exactly the same way as | |
+ # initial account confirmation) to be applied. Requires additional unconfirmed_email | |
+ # db field (see migrations). Until confirmed new email is stored in | |
+ # unconfirmed email column, and copied to email column on successful confirmation. | |
+ config.reconfirmable = true | |
+ | |
+ # Defines which key will be used when confirming an account | |
+ # config.confirmation_keys = [ :email ] | |
+ | |
+ # ==> Configuration for :rememberable | |
+ # The time the user will be remembered without asking for credentials again. | |
+ # config.remember_for = 2.weeks | |
+ | |
+ # If true, extends the user's remember period when remembered via cookie. | |
+ # config.extend_remember_period = false | |
+ | |
+ # Options to be passed to the created cookie. For instance, you can set | |
+ # :secure => true in order to force SSL only cookies. | |
+ # config.rememberable_options = {} | |
+ | |
+ # ==> Configuration for :validatable | |
+ # Range for password length. Default is 6..128. | |
+ # config.password_length = 6..128 | |
+ | |
+ # Email regex used to validate email formats. It simply asserts that | |
+ # an one (and only one) @ exists in the given string. This is mainly | |
+ # to give user feedback and not to assert the e-mail validity. | |
+ # config.email_regexp = /\A[^@]+@[^@]+\z/ | |
+ | |
+ # ==> Configuration for :timeoutable | |
+ # The time you want to timeout the user session without activity. After this | |
+ # time the user will be asked for credentials again. Default is 30 minutes. | |
+ # config.timeout_in = 30.minutes | |
+ | |
+ # If true, expires auth token on session timeout. | |
+ # config.expire_auth_token_on_timeout = false | |
+ | |
+ # ==> Configuration for :lockable | |
+ # Defines which strategy will be used to lock an account. | |
+ # :failed_attempts = Locks an account after a number of failed attempts to sign in. | |
+ # :none = No lock strategy. You should handle locking by yourself. | |
+ # config.lock_strategy = :failed_attempts | |
+ | |
+ # Defines which key will be used when locking and unlocking an account | |
+ # config.unlock_keys = [ :email ] | |
+ | |
+ # Defines which strategy will be used to unlock an account. | |
+ # :email = Sends an unlock link to the user email | |
+ # :time = Re-enables login after a certain amount of time (see :unlock_in below) | |
+ # :both = Enables both strategies | |
+ # :none = No unlock strategy. You should handle unlocking by yourself. | |
+ # config.unlock_strategy = :both | |
+ | |
+ # Number of authentication tries before locking an account if lock_strategy | |
+ # is failed attempts. | |
+ # config.maximum_attempts = 20 | |
+ | |
+ # Time interval to unlock the account if :time is enabled as unlock_strategy. | |
+ # config.unlock_in = 1.hour | |
+ | |
+ # ==> Configuration for :recoverable | |
+ # | |
+ # Defines which key will be used when recovering the password for an account | |
+ # config.reset_password_keys = [ :email ] | |
+ | |
+ # Time interval you can reset your password with a reset password key. | |
+ # Don't put a too small interval or your users won't have the time to | |
+ # change their passwords. | |
+ config.reset_password_within = 6.hours | |
+ | |
+ # ==> Configuration for :encryptable | |
+ # Allow you to use another encryption algorithm besides bcrypt (default). You can use | |
+ # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, | |
+ # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) | |
+ # and :restful_authentication_sha1 (then you should set stretches to 10, and copy | |
+ # REST_AUTH_SITE_KEY to pepper) | |
+ # config.encryptor = :sha512 | |
+ | |
+ # ==> Configuration for :token_authenticatable | |
+ # Defines name of the authentication token params key | |
+ # config.token_authentication_key = :auth_token | |
+ | |
+ # ==> Scopes configuration | |
+ # Turn scoped views on. Before rendering "sessions/new", it will first check for | |
+ # "users/sessions/new". It's turned off by default because it's slower if you | |
+ # are using only default views. | |
+ # config.scoped_views = false | |
+ | |
+ # Configure the default scope given to Warden. By default it's the first | |
+ # devise role declared in your routes (usually :user). | |
+ # config.default_scope = :user | |
+ | |
+ # Set this configuration to false if you want /users/sign_out to sign out | |
+ # only the current scope. By default, Devise signs out all scopes. | |
+ # config.sign_out_all_scopes = true | |
+ | |
+ # ==> Navigation configuration | |
+ # Lists the formats that should be treated as navigational. Formats like | |
+ # :html, should redirect to the sign in page when the user does not have | |
+ # access, but formats like :xml or :json, should return 401. | |
+ # | |
+ # If you have any extra navigational formats, like :iphone or :mobile, you | |
+ # should add them to the navigational formats lists. | |
+ # | |
+ # The "*/*" below is required to match Internet Explorer requests. | |
+ # config.navigational_formats = ["*/*", :html] | |
+ | |
+ # The default HTTP method used to sign out a resource. Default is :delete. | |
+ config.sign_out_via = Rails.env.test? ? :get : :delete | |
+ | |
+ # ==> OmniAuth | |
+ # Add a new OmniAuth provider. Check the wiki for more information on setting | |
+ # up on your models and hooks. | |
+ # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' | |
+ | |
+ # ==> Warden configuration | |
+ # If you want to use other strategies, that are not supported by Devise, or | |
+ # change the failure app, you can configure them inside the config.warden block. | |
+ # | |
+ # config.warden do |manager| | |
+ # manager.intercept_401 = false | |
+ # manager.default_strategies(:scope => :user).unshift :some_external_strategy | |
+ # end | |
+ | |
+ # ==> Mountable engine configurations | |
+ # When using Devise inside an engine, let's call it `MyEngine`, and this engine | |
+ # is mountable, there are some extra configurations to be taken into account. | |
+ # The following options are available, assuming the engine is mounted as: | |
+ # | |
+ # mount MyEngine, at: "/my_engine" | |
+ # | |
+ # The router that invoked `devise_for`, in the example above, would be: | |
+ # config.router_name = :my_engine | |
+ # | |
+ # When using omniauth, Devise cannot automatically set Omniauth path, | |
+ # so you need to do it manually. For the users scope, it would be: | |
+ # config.omniauth_path_prefix = "/my_engine/users/auth" | |
+end | |
\ No newline at end of file | |
diff --git a/config/initializers/generators.rb b/config/initializers/generators.rb | |
new file mode 100644 | |
index 0000000..4b3d83a | |
--- /dev/null | |
+++ b/config/initializers/generators.rb | |
@@ -0,0 +1,2 @@ | |
+Rails.application.config.generators do |g| | |
+end | |
diff --git a/config/initializers/rolify.rb b/config/initializers/rolify.rb | |
new file mode 100644 | |
index 0000000..c8df67a | |
--- /dev/null | |
+++ b/config/initializers/rolify.rb | |
@@ -0,0 +1,8 @@ | |
+Rolify.configure do |config| | |
+ # By default ORM adapter is ActiveRecord. uncomment to use mongoid | |
+ # config.use_mongoid | |
+ | |
+ # Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false | |
+ # Enable this feature _after_ running rake db:migrate as it relies on the roles table | |
+ # config.use_dynamic_shortcuts | |
+end | |
\ No newline at end of file | |
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb | |
index 6dff45d..9c262ce 100644 | |
--- a/config/initializers/secret_token.rb | |
+++ b/config/initializers/secret_token.rb | |
@@ -4,4 +4,4 @@ | |
# 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. | |
-RailsStripeMembershipSaas::Application.config.secret_token = 'e51445a72d86bf8dfd2dc7f928986bb4248cdb82efe406827ea66e506809912c874d6cc328334bc556c810890221c5860d3e102d60c627acaa42237dc82566d9' | |
+RailsStripeMembershipSaas::Application.config.secret_token = '830f5f35ed822db69710e145a72a4bd5fcdc79ed7bd17fa32fba75090bc0468dc6752fb12fb0b077a0a7e0940db17da10b3e26f8060154fd332dd4b212fd9468' | |
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb | |
index bae9160..74a6372 100644 | |
--- a/config/initializers/session_store.rb | |
+++ b/config/initializers/session_store.rb | |
@@ -1,6 +1,6 @@ | |
# Be sure to restart your server when you modify this file. | |
-RailsStripeMembershipSaas::Application.config.session_store :cookie_store, key: '_rails_stripe_membership_saas_session' | |
+RailsStripeMembershipSaas::Application.config.session_store :cookie_store, key: '_rails-stripe-membership-saas_session' | |
# Use the database for sessions instead of the cookie-based default, | |
# which shouldn't be used to store highly confidential information | |
diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb | |
new file mode 100644 | |
index 0000000..e3f8d09 | |
--- /dev/null | |
+++ b/config/initializers/simple_form.rb | |
@@ -0,0 +1,142 @@ | |
+# Use this setup block to configure all options available in SimpleForm. | |
+SimpleForm.setup do |config| | |
+ # Wrappers are used by the form builder to generate a | |
+ # complete input. You can remove any component from the | |
+ # wrapper, change the order or even add your own to the | |
+ # stack. The options given below are used to wrap the | |
+ # whole input. | |
+ config.wrappers :default, :class => :input, | |
+ :hint_class => :field_with_hint, :error_class => :field_with_errors do |b| | |
+ ## Extensions enabled by default | |
+ # Any of these extensions can be disabled for a | |
+ # given input by passing: `f.input EXTENSION_NAME => false`. | |
+ # You can make any of these extensions optional by | |
+ # renaming `b.use` to `b.optional`. | |
+ | |
+ # Determines whether to use HTML5 (:email, :url, ...) | |
+ # and required attributes | |
+ b.use :html5 | |
+ | |
+ # Calculates placeholders automatically from I18n | |
+ # You can also pass a string as f.input :placeholder => "Placeholder" | |
+ b.use :placeholder | |
+ | |
+ ## Optional extensions | |
+ # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup` | |
+ # to the input. If so, they will retrieve the values from the model | |
+ # if any exists. If you want to enable the lookup for any of those | |
+ # extensions by default, you can change `b.optional` to `b.use`. | |
+ | |
+ # Calculates maxlength from length validations for string inputs | |
+ b.optional :maxlength | |
+ | |
+ # Calculates pattern from format validations for string inputs | |
+ b.optional :pattern | |
+ | |
+ # Calculates min and max from length validations for numeric inputs | |
+ b.optional :min_max | |
+ | |
+ # Calculates readonly automatically from readonly attributes | |
+ b.optional :readonly | |
+ | |
+ ## Inputs | |
+ b.use :label_input | |
+ b.use :hint, :wrap_with => { :tag => :span, :class => :hint } | |
+ b.use :error, :wrap_with => { :tag => :span, :class => :error } | |
+ end | |
+ | |
+ # The default wrapper to be used by the FormBuilder. | |
+ config.default_wrapper = :default | |
+ | |
+ # Define the way to render check boxes / radio buttons with labels. | |
+ # Defaults to :nested for bootstrap config. | |
+ # :inline => input + label | |
+ # :nested => label > input | |
+ config.boolean_style = :nested | |
+ | |
+ # Default class for buttons | |
+ config.button_class = 'btn' | |
+ | |
+ # Method used to tidy up errors. Specify any Rails Array method. | |
+ # :first lists the first message for each field. | |
+ # Use :to_sentence to list all errors for each field. | |
+ # config.error_method = :first | |
+ | |
+ # Default tag used for error notification helper. | |
+ config.error_notification_tag = :div | |
+ | |
+ # CSS class to add for error notification helper. | |
+ config.error_notification_class = 'alert alert-error' | |
+ | |
+ # ID to add for error notification helper. | |
+ # config.error_notification_id = nil | |
+ | |
+ # 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 ] | |
+ | |
+ # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. | |
+ # config.collection_wrapper_tag = nil | |
+ | |
+ # You can define the class to use on all collection wrappers. Defaulting to none. | |
+ # config.collection_wrapper_class = nil | |
+ | |
+ # You can wrap each item in a collection of radio/check boxes with a tag, | |
+ # defaulting to :span. Please note that when using :boolean_style = :nested, | |
+ # SimpleForm will force this option to be a label. | |
+ # config.item_wrapper_tag = :span | |
+ | |
+ # You can define a class to use in all item wrappers. Defaulting to none. | |
+ # config.item_wrapper_class = nil | |
+ | |
+ # How the label text should be generated altogether with the required text. | |
+ # config.label_text = lambda { |label, required| "#{required} #{label}" } | |
+ | |
+ # You can define the class to use on all labels. Default is nil. | |
+ config.label_class = 'control-label' | |
+ | |
+ # You can define the class to use on all forms. Default is simple_form. | |
+ # config.form_class = :simple_form | |
+ | |
+ # You can define which elements should obtain additional classes | |
+ # config.generate_additional_classes_for = [:wrapper, :label, :input] | |
+ | |
+ # 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 = false | |
+ | |
+ # Collection of methods to detect if a file type was given. | |
+ # config.file_methods = [ :mounted_as, :file?, :public_filename ] | |
+ | |
+ # 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 } | |
+ | |
+ # Custom wrappers for input types. This should be a hash containing an input | |
+ # type as key and the wrapper that will be used for all inputs with specified type. | |
+ # config.wrapper_mappings = { :string => :prepend } | |
+ | |
+ # 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. | |
+ # config.translate_labels = true | |
+ | |
+ # Automatically discover new inputs in Rails' autoload path. | |
+ # config.inputs_discovery = true | |
+ | |
+ # Cache SimpleForm inputs discovery | |
+ # config.cache_discovery = !Rails.env.development? | |
+end | |
diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb | |
new file mode 100644 | |
index 0000000..1a22967 | |
--- /dev/null | |
+++ b/config/initializers/simple_form_bootstrap.rb | |
@@ -0,0 +1,45 @@ | |
+# Use this setup block to configure all options available in SimpleForm. | |
+SimpleForm.setup do |config| | |
+ config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b| | |
+ b.use :html5 | |
+ b.use :placeholder | |
+ b.use :label | |
+ b.wrapper :tag => 'div', :class => 'controls' do |ba| | |
+ ba.use :input | |
+ ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } | |
+ ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' } | |
+ end | |
+ end | |
+ | |
+ config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| | |
+ b.use :html5 | |
+ b.use :placeholder | |
+ b.use :label | |
+ b.wrapper :tag => 'div', :class => 'controls' do |input| | |
+ input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend| | |
+ prepend.use :input | |
+ end | |
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } | |
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } | |
+ end | |
+ end | |
+ | |
+ config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| | |
+ b.use :html5 | |
+ b.use :placeholder | |
+ b.use :label | |
+ b.wrapper :tag => 'div', :class => 'controls' do |input| | |
+ input.wrapper :tag => 'div', :class => 'input-append' do |append| | |
+ append.use :input | |
+ end | |
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } | |
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } | |
+ end | |
+ end | |
+ | |
+ # Wrappers for forms and inputs using the Twitter Bootstrap toolkit. | |
+ # Check the Bootstrap docs (http://twitter.github.com/bootstrap) | |
+ # to learn about the different styles for forms and inputs, | |
+ # buttons and other elements. | |
+ config.default_wrapper = :bootstrap | |
+end | |
diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb | |
new file mode 100644 | |
index 0000000..14dd345 | |
--- /dev/null | |
+++ b/config/initializers/stripe.rb | |
@@ -0,0 +1,9 @@ | |
+Stripe.api_key = ENV["STRIPE_API_KEY"] | |
+STRIPE_PUBLIC_KEY = ENV["STRIPE_PUBLIC_KEY"] | |
+ | |
+StripeEvent.setup do | |
+ subscribe 'customer.subscription.deleted' do |event| | |
+ user = User.find_by_customer_id(event.data.object.customer) | |
+ user.expire | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml | |
new file mode 100644 | |
index 0000000..7783a74 | |
--- /dev/null | |
+++ b/config/locales/devise.en.yml | |
@@ -0,0 +1,58 @@ | |
+# Additional translations at https://github.com/plataformatec/devise/wiki/I18n | |
+ | |
+en: | |
+ errors: | |
+ messages: | |
+ expired: "has expired, please request a new one" | |
+ not_found: "not found" | |
+ already_confirmed: "was already confirmed, please try signing in" | |
+ not_locked: "was not locked" | |
+ not_saved: | |
+ one: "1 error prohibited this %{resource} from being saved:" | |
+ other: "%{count} errors prohibited this %{resource} from being saved:" | |
+ | |
+ devise: | |
+ failure: | |
+ already_authenticated: 'You are already signed in.' | |
+ unauthenticated: 'You need to sign in or sign up before continuing.' | |
+ unconfirmed: 'You have to confirm your account before continuing.' | |
+ locked: 'Your account is locked.' | |
+ invalid: 'Invalid email or password.' | |
+ invalid_token: 'Invalid authentication token.' | |
+ timeout: 'Your session expired, please sign in again to continue.' | |
+ inactive: 'Your account was not activated yet.' | |
+ sessions: | |
+ signed_in: 'Signed in successfully.' | |
+ signed_out: 'Signed out successfully.' | |
+ passwords: | |
+ send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' | |
+ updated: 'Your password was changed successfully. You are now signed in.' | |
+ updated_not_active: 'Your password was changed successfully.' | |
+ send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." | |
+ no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." | |
+ confirmations: | |
+ send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' | |
+ send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.' | |
+ confirmed: 'Your account was successfully confirmed. You are now signed in.' | |
+ registrations: | |
+ signed_up: 'Welcome! You have signed up successfully.' | |
+ signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' | |
+ signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.' | |
+ signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.' | |
+ updated: 'You updated your account successfully.' | |
+ update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." | |
+ destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' | |
+ unlocks: | |
+ send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' | |
+ unlocked: 'Your account has been unlocked successfully. Please sign in to continue.' | |
+ send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.' | |
+ omniauth_callbacks: | |
+ success: 'Successfully authenticated from %{kind} account.' | |
+ failure: 'Could not authenticate you from %{kind} because "%{reason}".' | |
+ mailer: | |
+ confirmation_instructions: | |
+ subject: 'Confirmation instructions' | |
+ reset_password_instructions: | |
+ subject: 'Reset password instructions' | |
+ unlock_instructions: | |
+ subject: 'Unlock Instructions' | |
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml | |
new file mode 100644 | |
index 0000000..0df11fe | |
--- /dev/null | |
+++ b/config/locales/simple_form.en.yml | |
@@ -0,0 +1,26 @@ | |
+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: "Please review the problems below:" | |
+ # Labels and hints examples | |
+ # labels: | |
+ # defaults: | |
+ # password: 'Password' | |
+ # user: | |
+ # new: | |
+ # email: 'E-mail to sign in.' | |
+ # edit: | |
+ # email: 'E-mail.' | |
+ # hints: | |
+ # defaults: | |
+ # username: 'User name to sign in.' | |
+ # password: 'No special characters, please.' | |
+ | |
diff --git a/config/routes.rb b/config/routes.rb | |
index 8841005..11f6370 100644 | |
--- a/config/routes.rb | |
+++ b/config/routes.rb | |
@@ -1,58 +1,12 @@ | |
RailsStripeMembershipSaas::Application.routes.draw do | |
- # The priority is based upon order of creation: | |
- # first created -> highest priority. | |
- | |
- # Sample of regular route: | |
- # match 'products/:id' => 'catalog#view' | |
- # Keep in mind you can assign values other than :controller and :action | |
- | |
- # Sample of named route: | |
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase | |
- # This route can be invoked with purchase_url(:id => product.id) | |
- | |
- # Sample resource route (maps HTTP verbs to controller actions automatically): | |
- # resources :products | |
- | |
- # Sample resource route with options: | |
- # resources :products do | |
- # member do | |
- # get 'short' | |
- # post 'toggle' | |
- # end | |
- # | |
- # collection do | |
- # get 'sold' | |
- # end | |
- # end | |
- | |
- # Sample resource route with sub-resources: | |
- # resources :products do | |
- # resources :comments, :sales | |
- # resource :seller | |
- # end | |
- | |
- # Sample resource route with more complex sub-resources | |
- # resources :products do | |
- # resources :comments | |
- # resources :sales do | |
- # get 'recent', :on => :collection | |
- # end | |
- # end | |
- | |
- # Sample resource route within a namespace: | |
- # namespace :admin do | |
- # # Directs /admin/products/* to Admin::ProductsController | |
- # # (app/controllers/admin/products_controller.rb) | |
- # resources :products | |
- # end | |
- | |
- # You can have the root of your site routed with "root" | |
- # just remember to delete public/index.html. | |
- # root :to => 'welcome#index' | |
- | |
- # See how all your routes lay out with "rake routes" | |
- | |
- # This is a legacy wild controller route that's not recommended for RESTful applications. | |
- # Note: This route will make all actions in every controller accessible via GET requests. | |
- # match ':controller(/:action(/:id))(.:format)' | |
-end | |
+ mount StripeEvent::Engine => '/stripe' | |
+ get "content/gold" | |
+ get "content/silver" | |
+ get "content/platinum" | |
+ authenticated :user do | |
+ root :to => 'home#index' | |
+ end | |
+ root :to => "home#index" | |
+ devise_for :users, :controllers => { :registrations => 'registrations' } | |
+ resources :users | |
+end | |
\ No newline at end of file | |
diff --git a/db/migrate/20121012222507_devise_create_users.rb b/db/migrate/20121012222507_devise_create_users.rb | |
new file mode 100644 | |
index 0000000..2099d99 | |
--- /dev/null | |
+++ b/db/migrate/20121012222507_devise_create_users.rb | |
@@ -0,0 +1,46 @@ | |
+class DeviseCreateUsers < ActiveRecord::Migration | |
+ def change | |
+ create_table(:users) do |t| | |
+ ## Database authenticatable | |
+ t.string :email, :null => false, :default => "" | |
+ t.string :encrypted_password, :null => false, :default => "" | |
+ | |
+ ## Recoverable | |
+ t.string :reset_password_token | |
+ t.datetime :reset_password_sent_at | |
+ | |
+ ## Rememberable | |
+ t.datetime :remember_created_at | |
+ | |
+ ## Trackable | |
+ t.integer :sign_in_count, :default => 0 | |
+ t.datetime :current_sign_in_at | |
+ t.datetime :last_sign_in_at | |
+ t.string :current_sign_in_ip | |
+ t.string :last_sign_in_ip | |
+ | |
+ ## Confirmable | |
+ # t.string :confirmation_token | |
+ # t.datetime :confirmed_at | |
+ # t.datetime :confirmation_sent_at | |
+ # t.string :unconfirmed_email # Only if using reconfirmable | |
+ | |
+ ## Lockable | |
+ # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts | |
+ # t.string :unlock_token # Only if unlock strategy is :email or :both | |
+ # t.datetime :locked_at | |
+ | |
+ ## Token authenticatable | |
+ # t.string :authentication_token | |
+ | |
+ | |
+ t.timestamps | |
+ end | |
+ | |
+ add_index :users, :email, :unique => true | |
+ add_index :users, :reset_password_token, :unique => true | |
+ # add_index :users, :confirmation_token, :unique => true | |
+ # add_index :users, :unlock_token, :unique => true | |
+ # add_index :users, :authentication_token, :unique => true | |
+ end | |
+end | |
diff --git a/db/migrate/20121012222509_add_name_to_users.rb b/db/migrate/20121012222509_add_name_to_users.rb | |
new file mode 100644 | |
index 0000000..bac750e | |
--- /dev/null | |
+++ b/db/migrate/20121012222509_add_name_to_users.rb | |
@@ -0,0 +1,5 @@ | |
+class AddNameToUsers < ActiveRecord::Migration | |
+ def change | |
+ add_column :users, :name, :string | |
+ end | |
+end | |
diff --git a/db/migrate/20121012222516_rolify_create_roles.rb b/db/migrate/20121012222516_rolify_create_roles.rb | |
new file mode 100644 | |
index 0000000..999c94a | |
--- /dev/null | |
+++ b/db/migrate/20121012222516_rolify_create_roles.rb | |
@@ -0,0 +1,19 @@ | |
+class RolifyCreateRoles < ActiveRecord::Migration | |
+ def change | |
+ create_table(:roles) do |t| | |
+ t.string :name | |
+ t.references :resource, :polymorphic => true | |
+ | |
+ t.timestamps | |
+ end | |
+ | |
+ create_table(:users_roles, :id => false) do |t| | |
+ t.references :user | |
+ t.references :role | |
+ end | |
+ | |
+ add_index(:roles, :name) | |
+ add_index(:roles, [ :name, :resource_type, :resource_id ]) | |
+ add_index(:users_roles, [ :user_id, :role_id ]) | |
+ end | |
+end | |
diff --git a/db/migrate/20121017180539_add_stripe_to_users.rb b/db/migrate/20121017180539_add_stripe_to_users.rb | |
new file mode 100644 | |
index 0000000..a0cf67a | |
--- /dev/null | |
+++ b/db/migrate/20121017180539_add_stripe_to_users.rb | |
@@ -0,0 +1,6 @@ | |
+class AddStripeToUsers < ActiveRecord::Migration | |
+ def change | |
+ add_column :users, :customer_id, :string | |
+ add_column :users, :last_4_digits, :string | |
+ end | |
+end | |
diff --git a/db/schema.rb b/db/schema.rb | |
new file mode 100644 | |
index 0000000..81c125a | |
--- /dev/null | |
+++ b/db/schema.rb | |
@@ -0,0 +1,55 @@ | |
+# 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 => 20121017180539) do | |
+ | |
+ create_table "roles", :force => true do |t| | |
+ t.string "name" | |
+ t.integer "resource_id" | |
+ t.string "resource_type" | |
+ t.datetime "created_at", :null => false | |
+ t.datetime "updated_at", :null => false | |
+ end | |
+ | |
+ add_index "roles", ["name", "resource_type", "resource_id"], :name => "index_roles_on_name_and_resource_type_and_resource_id" | |
+ add_index "roles", ["name"], :name => "index_roles_on_name" | |
+ | |
+ create_table "users", :force => true do |t| | |
+ t.string "email", :default => "", :null => false | |
+ t.string "encrypted_password", :default => "", :null => false | |
+ t.string "reset_password_token" | |
+ t.datetime "reset_password_sent_at" | |
+ t.datetime "remember_created_at" | |
+ t.integer "sign_in_count", :default => 0 | |
+ t.datetime "current_sign_in_at" | |
+ t.datetime "last_sign_in_at" | |
+ t.string "current_sign_in_ip" | |
+ t.string "last_sign_in_ip" | |
+ t.datetime "created_at", :null => false | |
+ t.datetime "updated_at", :null => false | |
+ t.string "name" | |
+ t.string "customer_id" | |
+ t.string "last_4_digits" | |
+ end | |
+ | |
+ add_index "users", ["email"], :name => "index_users_on_email", :unique => true | |
+ add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true | |
+ | |
+ create_table "users_roles", :id => false, :force => true do |t| | |
+ t.integer "user_id" | |
+ t.integer "role_id" | |
+ end | |
+ | |
+ add_index "users_roles", ["user_id", "role_id"], :name => "index_users_roles_on_user_id_and_role_id" | |
+ | |
+end | |
diff --git a/db/seeds.rb b/db/seeds.rb | |
index 4edb1e8..8883e18 100644 | |
--- a/db/seeds.rb | |
+++ b/db/seeds.rb | |
@@ -5,3 +5,21 @@ | |
# | |
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) | |
# Mayor.create(name: 'Emanuel', city: cities.first) | |
+puts 'CREATING ROLES' | |
+Role.create([ | |
+ { :name => 'admin' }, | |
+ { :name => 'silver' }, | |
+ { :name => 'gold' }, | |
+ { :name => 'platinum' } | |
+], :without_protection => true) | |
+puts 'SETTING UP DEFAULT USER LOGIN' | |
+user = User.create! :name => 'First User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user.add_role :admin | |
+puts 'admin created: ' << user.name | |
+user2 = User.create! :name => 'Silver User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user2.add_role :silver | |
+user3 = User.create! :name => 'Gold User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user3.add_role :gold | |
+user4 = User.create! :name => 'Platinum User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please' | |
+user4.add_role :platinum | |
+puts "added users: #{user2.name}, #{user3.name}, #{user4.name}" | |
\ No newline at end of file | |
diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP | |
deleted file mode 100644 | |
index fe41f5c..0000000 | |
--- a/doc/README_FOR_APP | |
+++ /dev/null | |
@@ -1,2 +0,0 @@ | |
-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. | |
diff --git a/features/step_definitions/email_steps.rb b/features/step_definitions/email_steps.rb | |
new file mode 100644 | |
index 0000000..12bcb3f | |
--- /dev/null | |
+++ b/features/step_definitions/email_steps.rb | |
@@ -0,0 +1,206 @@ | |
+# Commonly used email steps | |
+# | |
+# To add your own steps make a custom_email_steps.rb | |
+# The provided methods are: | |
+# | |
+# last_email_address | |
+# reset_mailer | |
+# open_last_email | |
+# visit_in_email | |
+# unread_emails_for | |
+# mailbox_for | |
+# current_email | |
+# open_email | |
+# read_emails_for | |
+# find_email | |
+# | |
+# General form for email scenarios are: | |
+# - clear the email queue (done automatically by email_spec) | |
+# - execute steps that sends an email | |
+# - check the user received an/no/[0-9] emails | |
+# - open the email | |
+# - inspect the email contents | |
+# - interact with the email (e.g. click links) | |
+# | |
+# The Cucumber steps below are setup in this order. | |
+ | |
+module EmailHelpers | |
+ def current_email_address | |
+ # Replace with your a way to find your current email. e.g @current_user.email | |
+ # last_email_address will return the last email address used by email spec to find an email. | |
+ # Note that last_email_address will be reset after each Scenario. | |
+ last_email_address || "[email protected]" | |
+ end | |
+end | |
+ | |
+World(EmailHelpers) | |
+ | |
+# | |
+# Reset the e-mail queue within a scenario. | |
+# This is done automatically before each scenario. | |
+# | |
+ | |
+Given /^(?:a clear email queue|no emails have been sent)$/ do | |
+ reset_mailer | |
+end | |
+ | |
+# | |
+# Check how many emails have been sent/received | |
+# | |
+ | |
+Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails?$/ do |address, amount| | |
+ unread_emails_for(address).size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^(?:I|they|"([^"]*?)") should have (an|no|\d+) emails?$/ do |address, amount| | |
+ mailbox_for(address).size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject "([^"]*?)"$/ do |address, amount, subject| | |
+ unread_emails_for(address).select { |m| m.subject =~ Regexp.new(Regexp.escape(subject)) }.size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject \/([^"]*?)\/$/ do |address, amount, subject| | |
+ unread_emails_for(address).select { |m| m.subject =~ Regexp.new(subject) }.size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^(?:I|they|"([^"]*?)") should receive an email with the following body:$/ do |address, expected_body| | |
+ open_email(address, :with_text => expected_body) | |
+end | |
+ | |
+# | |
+# Accessing emails | |
+# | |
+ | |
+# Opens the most recently received email | |
+When /^(?:I|they|"([^"]*?)") opens? the email$/ do |address| | |
+ open_email(address) | |
+end | |
+ | |
+When /^(?:I|they|"([^"]*?)") opens? the email with subject "([^"]*?)"$/ do |address, subject| | |
+ open_email(address, :with_subject => subject) | |
+end | |
+ | |
+When /^(?:I|they|"([^"]*?)") opens? the email with subject \/([^"]*?)\/$/ do |address, subject| | |
+ open_email(address, :with_subject => Regexp.new(subject)) | |
+end | |
+ | |
+When /^(?:I|they|"([^"]*?)") opens? the email with text "([^"]*?)"$/ do |address, text| | |
+ open_email(address, :with_text => text) | |
+end | |
+ | |
+When /^(?:I|they|"([^"]*?)") opens? the email with text \/([^"]*?)\/$/ do |address, text| | |
+ open_email(address, :with_text => Regexp.new(text)) | |
+end | |
+ | |
+# | |
+# Inspect the Email Contents | |
+# | |
+ | |
+Then /^(?:I|they) should see "([^"]*?)" in the email subject$/ do |text| | |
+ current_email.should have_subject(text) | |
+end | |
+ | |
+Then /^(?:I|they) should see \/([^"]*?)\/ in the email subject$/ do |text| | |
+ current_email.should have_subject(Regexp.new(text)) | |
+end | |
+ | |
+Then /^(?:I|they) should see "([^"]*?)" in the email body$/ do |text| | |
+ current_email.default_part_body.to_s.should include(text) | |
+end | |
+ | |
+Then /^(?:I|they) should see \/([^"]*?)\/ in the email body$/ do |text| | |
+ current_email.default_part_body.to_s.should =~ Regexp.new(text) | |
+end | |
+ | |
+Then /^(?:I|they) should see the email delivered from "([^"]*?)"$/ do |text| | |
+ current_email.should be_delivered_from(text) | |
+end | |
+ | |
+Then /^(?:I|they) should see "([^\"]*)" in the email "([^"]*?)" header$/ do |text, name| | |
+ current_email.should have_header(name, text) | |
+end | |
+ | |
+Then /^(?:I|they) should see \/([^\"]*)\/ in the email "([^"]*?)" header$/ do |text, name| | |
+ current_email.should have_header(name, Regexp.new(text)) | |
+end | |
+ | |
+Then /^I should see it is a multi\-part email$/ do | |
+ current_email.should be_multipart | |
+end | |
+ | |
+Then /^(?:I|they) should see "([^"]*?)" in the email html part body$/ do |text| | |
+ current_email.html_part.body.to_s.should include(text) | |
+end | |
+ | |
+Then /^(?:I|they) should see "([^"]*?)" in the email text part body$/ do |text| | |
+ current_email.text_part.body.to_s.should include(text) | |
+end | |
+ | |
+# | |
+# Inspect the Email Attachments | |
+# | |
+ | |
+Then /^(?:I|they) should see (an|no|\d+) attachments? with the email$/ do |amount| | |
+ current_email_attachments.size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^there should be (an|no|\d+) attachments? named "([^"]*?)"$/ do |amount, filename| | |
+ current_email_attachments.select { |a| a.filename == filename }.size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^attachment (\d+) should be named "([^"]*?)"$/ do |index, filename| | |
+ current_email_attachments[(index.to_i - 1)].filename.should == filename | |
+end | |
+ | |
+Then /^there should be (an|no|\d+) attachments? of type "([^"]*?)"$/ do |amount, content_type| | |
+ current_email_attachments.select { |a| a.content_type.include?(content_type) }.size.should == parse_email_count(amount) | |
+end | |
+ | |
+Then /^attachment (\d+) should be of type "([^"]*?)"$/ do |index, content_type| | |
+ current_email_attachments[(index.to_i - 1)].content_type.should include(content_type) | |
+end | |
+ | |
+Then /^all attachments should not be blank$/ do | |
+ current_email_attachments.each do |attachment| | |
+ attachment.read.size.should_not == 0 | |
+ end | |
+end | |
+ | |
+Then /^show me a list of email attachments$/ do | |
+ EmailSpec::EmailViewer::save_and_open_email_attachments_list(current_email) | |
+end | |
+ | |
+# | |
+# Interact with Email Contents | |
+# | |
+ | |
+When /^(?:I|they) follow "([^"]*?)" in the email$/ do |link| | |
+ visit_in_email(link) | |
+end | |
+ | |
+When /^(?:I|they) click the first link in the email$/ do | |
+ click_first_link_in_email | |
+end | |
+ | |
+# | |
+# Debugging | |
+# These only work with Rails and OSx ATM since EmailViewer uses RAILS_ROOT and OSx's 'open' command. | |
+# Patches accepted. ;) | |
+# | |
+ | |
+Then /^save and open current email$/ do | |
+ EmailSpec::EmailViewer::save_and_open_email(current_email) | |
+end | |
+ | |
+Then /^save and open all text emails$/ do | |
+ EmailSpec::EmailViewer::save_and_open_all_text_emails | |
+end | |
+ | |
+Then /^save and open all html emails$/ do | |
+ EmailSpec::EmailViewer::save_and_open_all_html_emails | |
+end | |
+ | |
+Then /^save and open all raw emails$/ do | |
+ EmailSpec::EmailViewer::save_and_open_all_raw_emails | |
+end | |
diff --git a/features/step_definitions/user_steps.rb b/features/step_definitions/user_steps.rb | |
new file mode 100644 | |
index 0000000..4575333 | |
--- /dev/null | |
+++ b/features/step_definitions/user_steps.rb | |
@@ -0,0 +1,187 @@ | |
+### UTILITY METHODS ### | |
+ | |
+def create_visitor | |
+ @visitor ||= { :name => "Testy McUserton", :email => "[email protected]", | |
+ :password => "please", :password_confirmation => "please" } | |
+end | |
+ | |
+def find_user | |
+ @user ||= User.first conditions: {:email => @visitor[:email]} | |
+end | |
+ | |
+def create_unconfirmed_user | |
+ create_visitor | |
+ delete_user | |
+ sign_up | |
+ visit '/users/sign_out' | |
+end | |
+ | |
+def create_user | |
+ create_visitor | |
+ delete_user | |
+ @user = FactoryGirl.create(:user, email: @visitor[:email]) | |
+ @user.add_role("") | |
+end | |
+ | |
+def delete_user | |
+ @user ||= User.first conditions: {:email => @visitor[:email]} | |
+ @user.destroy unless @user.nil? | |
+end | |
+ | |
+def sign_up | |
+ delete_user | |
+ visit '/users/sign_up' | |
+ fill_in "Name", :with => @visitor[:name] | |
+ fill_in "Email", :with => @visitor[:email] | |
+ fill_in "Password", :with => @visitor[:password] | |
+ fill_in "Password confirmation", :with => @visitor[:password_confirmation] | |
+ click_button "Sign up" | |
+ find_user | |
+end | |
+ | |
+def sign_in | |
+ visit '/users/sign_in' | |
+ fill_in "Email", :with => @visitor[:email] | |
+ fill_in "Password", :with => @visitor[:password] | |
+ click_button "Sign in" | |
+end | |
+ | |
+### GIVEN ### | |
+Given /^I am not logged in$/ do | |
+ visit destroy_user_session_path | |
+end | |
+ | |
+Given /^I am logged in$/ do | |
+ create_user | |
+ sign_in | |
+end | |
+ | |
+Given /^I exist as a user$/ do | |
+ create_user | |
+end | |
+ | |
+Given /^I do not exist as a user$/ do | |
+ create_visitor | |
+ delete_user | |
+end | |
+ | |
+Given /^I exist as an unconfirmed user$/ do | |
+ create_unconfirmed_user | |
+end | |
+ | |
+### WHEN ### | |
+When /^I sign in with valid credentials$/ do | |
+ create_visitor | |
+ sign_in | |
+end | |
+ | |
+When /^I sign out$/ do | |
+ visit '/users/sign_out' | |
+end | |
+ | |
+When /^I sign up with valid user data$/ do | |
+ create_visitor | |
+ sign_up | |
+end | |
+ | |
+When /^I sign up with an invalid email$/ do | |
+ create_visitor | |
+ @visitor = @visitor.merge(:email => "notanemail") | |
+ sign_up | |
+end | |
+ | |
+When /^I sign up without a password confirmation$/ do | |
+ create_visitor | |
+ @visitor = @visitor.merge(:password_confirmation => "") | |
+ sign_up | |
+end | |
+ | |
+When /^I sign up without a password$/ do | |
+ create_visitor | |
+ @visitor = @visitor.merge(:password => "") | |
+ sign_up | |
+end | |
+ | |
+When /^I sign up with a mismatched password confirmation$/ do | |
+ create_visitor | |
+ @visitor = @visitor.merge(:password_confirmation => "please123") | |
+ sign_up | |
+end | |
+ | |
+When /^I return to the site$/ do | |
+ visit '/' | |
+end | |
+ | |
+When /^I sign in with a wrong email$/ do | |
+ @visitor = @visitor.merge(:email => "[email protected]") | |
+ sign_in | |
+end | |
+ | |
+When /^I sign in with a wrong password$/ do | |
+ @visitor = @visitor.merge(:password => "wrongpass") | |
+ sign_in | |
+end | |
+ | |
+When /^I edit my account details$/ do | |
+ click_link "Edit account" | |
+ fill_in "Name", :with => "newname" | |
+ fill_in "Current password", :with => @visitor[:password] | |
+ click_button "Update" | |
+end | |
+ | |
+### THEN ### | |
+Then /^I should be signed in$/ do | |
+ page.should have_content "Logout" | |
+ page.should_not have_content "Sign up" | |
+ page.should_not have_content "Login" | |
+end | |
+ | |
+Then /^I should be signed out$/ do | |
+ page.should have_content "Login" | |
+ page.should_not have_content "Logout" | |
+end | |
+ | |
+Then /^I see an unconfirmed account message$/ do | |
+ page.should have_content "You have to confirm your account before continuing." | |
+end | |
+ | |
+Then /^I see a successful sign in message$/ do | |
+ page.should have_content "Signed in successfully." | |
+end | |
+ | |
+Then /^I should see a successful sign up message$/ do | |
+ page.should have_content "Welcome! You have signed up successfully." | |
+end | |
+ | |
+Then /^I should see an invalid email message$/ do | |
+ page.should have_content "is invalid" | |
+end | |
+ | |
+Then /^I should see a missing password message$/ do | |
+ page.should have_content "can't be blank" | |
+end | |
+ | |
+Then /^I should see a missing password confirmation message$/ do | |
+ page.should have_content "doesn't match confirmation" | |
+end | |
+ | |
+Then /^I should see a mismatched password message$/ do | |
+ page.should have_content "doesn't match confirmation" | |
+end | |
+ | |
+Then /^I should see a signed out message$/ do | |
+ page.should have_content "Signed out successfully." | |
+end | |
+ | |
+Then /^I see an invalid login message$/ do | |
+ page.should have_content "Invalid email or password." | |
+end | |
+ | |
+Then /^I should see an account edited message$/ do | |
+ page.should have_content "You updated your account successfully." | |
+end | |
+ | |
+Then /^I should see my name$/ do | |
+ create_user | |
+ page.should have_content @user[:name] | |
+end | |
diff --git a/features/support/email_spec.rb b/features/support/email_spec.rb | |
new file mode 100644 | |
index 0000000..797d3b2 | |
--- /dev/null | |
+++ b/features/support/email_spec.rb | |
@@ -0,0 +1 @@ | |
+require 'email_spec/cucumber' | |
diff --git a/features/support/env.rb b/features/support/env.rb | |
new file mode 100644 | |
index 0000000..29f204c | |
--- /dev/null | |
+++ b/features/support/env.rb | |
@@ -0,0 +1,59 @@ | |
+# 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 | |
+# # { :except => [:widgets] } may not do what you expect here | |
+# # as tCucumber::Rails::Database.javascript_strategy overrides | |
+# # this setting. | |
+# DatabaseCleaner.strategy = :truncation | |
+# 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 | |
+ | |
diff --git a/features/support/paths.rb b/features/support/paths.rb | |
new file mode 100644 | |
index 0000000..cc4378d | |
--- /dev/null | |
+++ b/features/support/paths.rb | |
@@ -0,0 +1,39 @@ | |
+module NavigationHelpers | |
+ # Maps a name to a path. Used by the | |
+ # | |
+ # When /^I go to (.+)$/ do |page_name| | |
+ # | |
+ # step definition in web_steps.rb | |
+ # | |
+ def path_to(page_name) | |
+ case page_name | |
+ | |
+ when /the home\s?page/ | |
+ '/' | |
+ | |
+ when /the sign up page/ | |
+ '/users/sign_up' | |
+ | |
+ when /the sign in page/ | |
+ '/users/sign_in' | |
+ | |
+ # Add more mappings here. | |
+ # Here is an example that pulls values out of the Regexp: | |
+ # | |
+ # when /^(.*)'s profile page$/i | |
+ # user_profile_path(User.find_by_login($1)) | |
+ | |
+ else | |
+ begin | |
+ page_name =~ /the (.*) page/ | |
+ path_components = $1.split(/\s+/) | |
+ self.send(path_components.push('path').join('_').to_sym) | |
+ rescue Object => e | |
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" + | |
+ "Now, go and add a mapping in #{__FILE__}" | |
+ end | |
+ end | |
+ end | |
+end | |
+ | |
+World(NavigationHelpers) | |
diff --git a/features/users/sign_in.feature b/features/users/sign_in.feature | |
new file mode 100644 | |
index 0000000..7c9ce76 | |
--- /dev/null | |
+++ b/features/users/sign_in.feature | |
@@ -0,0 +1,34 @@ | |
+Feature: Sign in | |
+ In order to get access to protected sections of the site | |
+ A user | |
+ Should be able to sign in | |
+ | |
+ Scenario: User is not signed up | |
+ Given I do not exist as a user | |
+ When I sign in with valid credentials | |
+ Then I see an invalid login message | |
+ And I should be signed out | |
+ | |
+ Scenario: User signs in successfully | |
+ Given I exist as a user | |
+ And I am not logged in | |
+ When I sign in with valid credentials | |
+ Then I see a successful sign in message | |
+ When I return to the site | |
+ Then I should be signed in | |
+ | |
+ Scenario: User enters wrong email | |
+ Given I exist as a user | |
+ And I am not logged in | |
+ When I sign in with a wrong email | |
+ Then I see an invalid login message | |
+ And I should be signed out | |
+ | |
+ Scenario: User enters wrong password | |
+ Given I exist as a user | |
+ And I am not logged in | |
+ When I sign in with a wrong password | |
+ Then I see an invalid login message | |
+ And I should be signed out | |
+ | |
+ | |
\ No newline at end of file | |
diff --git a/features/users/sign_out.feature b/features/users/sign_out.feature | |
new file mode 100644 | |
index 0000000..9e786d2 | |
--- /dev/null | |
+++ b/features/users/sign_out.feature | |
@@ -0,0 +1,11 @@ | |
+Feature: Sign out | |
+ To protect my account from unauthorized access | |
+ A signed in user | |
+ Should be able to sign out | |
+ | |
+ Scenario: User signs out | |
+ Given I am logged in | |
+ When I sign out | |
+ Then I should see a signed out message | |
+ When I return to the site | |
+ Then I should be signed out | |
diff --git a/features/users/sign_up.feature b/features/users/sign_up.feature | |
new file mode 100644 | |
index 0000000..56606c3 | |
--- /dev/null | |
+++ b/features/users/sign_up.feature | |
@@ -0,0 +1,27 @@ | |
+Feature: Sign up | |
+ In order to get access to protected sections of the site | |
+ As a user | |
+ I want to be able to sign up | |
+ | |
+ Background: | |
+ Given I am not logged in | |
+ | |
+ Scenario: User signs up with valid data | |
+ When I sign up with valid user data | |
+ Then I should see a successful sign up message | |
+ | |
+ Scenario: User signs up with invalid email | |
+ When I sign up with an invalid email | |
+ Then I should see an invalid email message | |
+ | |
+ Scenario: User signs up without password | |
+ When I sign up without a password | |
+ Then I should see a missing password message | |
+ | |
+ Scenario: User signs up without password confirmation | |
+ When I sign up without a password confirmation | |
+ Then I should see a missing password confirmation message | |
+ | |
+ Scenario: User signs up with mismatched password and confirmation | |
+ When I sign up with a mismatched password confirmation | |
+ Then I should see a mismatched password message | |
diff --git a/features/users/user_edit.feature b/features/users/user_edit.feature | |
new file mode 100644 | |
index 0000000..aea37f0 | |
--- /dev/null | |
+++ b/features/users/user_edit.feature | |
@@ -0,0 +1,9 @@ | |
+Feature: Edit User | |
+ As a registered user of the website | |
+ I want to edit my user profile | |
+ so I can change my username | |
+ | |
+ Scenario: I sign in and edit my account | |
+ Given I am logged in | |
+ When I edit my account details | |
+ Then I should see an account edited message | |
diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake | |
new file mode 100644 | |
index 0000000..83f7947 | |
--- /dev/null | |
+++ b/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 | |
diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb | |
new file mode 100644 | |
index 0000000..201a069 | |
--- /dev/null | |
+++ b/lib/templates/erb/scaffold/_form.html.erb | |
@@ -0,0 +1,13 @@ | |
+<%%= simple_form_for(@<%= singular_table_name %>) do |f| %> | |
+ <%%= f.error_notification %> | |
+ | |
+ <div class="form-inputs"> | |
+ <%- attributes.each do |attribute| -%> | |
+ <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> | |
+ <%- end -%> | |
+ </div> | |
+ | |
+ <div class="form-actions"> | |
+ <%%= f.button :submit %> | |
+ </div> | |
+<%% end %> | |
diff --git a/log/.gitkeep b/log/.gitkeep | |
deleted file mode 100644 | |
index e69de29..0000000 | |
diff --git a/public/humans.txt b/public/humans.txt | |
new file mode 100644 | |
index 0000000..8405530 | |
--- /dev/null | |
+++ b/public/humans.txt | |
@@ -0,0 +1,20 @@ | |
+/* the humans responsible & colophon */ | |
+/* humanstxt.org */ | |
+ | |
+ | |
+/* TEAM */ | |
+ <your title>: <your name> | |
+ Site: | |
+ Twitter: | |
+ Location: | |
+ | |
+/* THANKS */ | |
+ Daniel Kehoe (@rails_apps) for the RailsApps project | |
+ | |
+/* SITE */ | |
+ Standards: HTML5, CSS3 | |
+ Components: jQuery | |
+ Software: Ruby on Rails | |
+ | |
+/* GENERATED BY */ | |
+ RailsApps application template: http://railsapps.github.com/ | |
\ No newline at end of file | |
diff --git a/public/index.html b/public/index.html | |
deleted file mode 100644 | |
index a1d5099..0000000 | |
--- a/public/index.html | |
+++ /dev/null | |
@@ -1,241 +0,0 @@ | |
-<!DOCTYPE html> | |
-<html> | |
- <head> | |
- <title>Ruby on Rails: Welcome aboard</title> | |
- <style type="text/css" media="screen"> | |
- body { | |
- margin: 0; | |
- margin-bottom: 25px; | |
- padding: 0; | |
- background-color: #f0f0f0; | |
- font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana"; | |
- font-size: 13px; | |
- color: #333; | |
- } | |
- | |
- h1 { | |
- font-size: 28px; | |
- color: #000; | |
- } | |
- | |
- a {color: #03c} | |
- a:hover { | |
- background-color: #03c; | |
- color: white; | |
- text-decoration: none; | |
- } | |
- | |
- | |
- #page { | |
- background-color: #f0f0f0; | |
- width: 750px; | |
- margin: 0; | |
- margin-left: auto; | |
- margin-right: auto; | |
- } | |
- | |
- #content { | |
- float: left; | |
- background-color: white; | |
- border: 3px solid #aaa; | |
- border-top: none; | |
- padding: 25px; | |
- width: 500px; | |
- } | |
- | |
- #sidebar { | |
- float: right; | |
- width: 175px; | |
- } | |
- | |
- #footer { | |
- clear: both; | |
- } | |
- | |
- #header, #about, #getting-started { | |
- padding-left: 75px; | |
- padding-right: 30px; | |
- } | |
- | |
- | |
- #header { | |
- background-image: url("assets/rails.png"); | |
- background-repeat: no-repeat; | |
- background-position: top left; | |
- height: 64px; | |
- } | |
- #header h1, #header h2 {margin: 0} | |
- #header h2 { | |
- color: #888; | |
- font-weight: normal; | |
- font-size: 16px; | |
- } | |
- | |
- | |
- #about h3 { | |
- margin: 0; | |
- margin-bottom: 10px; | |
- font-size: 14px; | |
- } | |
- | |
- #about-content { | |
- background-color: #ffd; | |
- border: 1px solid #fc0; | |
- margin-left: -55px; | |
- margin-right: -10px; | |
- } | |
- #about-content table { | |
- margin-top: 10px; | |
- margin-bottom: 10px; | |
- font-size: 11px; | |
- border-collapse: collapse; | |
- } | |
- #about-content td { | |
- padding: 10px; | |
- padding-top: 3px; | |
- padding-bottom: 3px; | |
- } | |
- #about-content td.name {color: #555} | |
- #about-content td.value {color: #000} | |
- | |
- #about-content ul { | |
- padding: 0; | |
- list-style-type: none; | |
- } | |
- | |
- #about-content.failure { | |
- background-color: #fcc; | |
- border: 1px solid #f00; | |
- } | |
- #about-content.failure p { | |
- margin: 0; | |
- padding: 10px; | |
- } | |
- | |
- | |
- #getting-started { | |
- border-top: 1px solid #ccc; | |
- margin-top: 25px; | |
- padding-top: 15px; | |
- } | |
- #getting-started h1 { | |
- margin: 0; | |
- font-size: 20px; | |
- } | |
- #getting-started h2 { | |
- margin: 0; | |
- font-size: 14px; | |
- font-weight: normal; | |
- color: #333; | |
- margin-bottom: 25px; | |
- } | |
- #getting-started ol { | |
- margin-left: 0; | |
- padding-left: 0; | |
- } | |
- #getting-started li { | |
- font-size: 18px; | |
- color: #888; | |
- margin-bottom: 25px; | |
- } | |
- #getting-started li h2 { | |
- margin: 0; | |
- font-weight: normal; | |
- font-size: 18px; | |
- color: #333; | |
- } | |
- #getting-started li p { | |
- color: #555; | |
- font-size: 13px; | |
- } | |
- | |
- | |
- #sidebar ul { | |
- margin-left: 0; | |
- padding-left: 0; | |
- } | |
- #sidebar ul h3 { | |
- margin-top: 25px; | |
- font-size: 16px; | |
- padding-bottom: 10px; | |
- border-bottom: 1px solid #ccc; | |
- } | |
- #sidebar li { | |
- list-style-type: none; | |
- } | |
- #sidebar ul.links li { | |
- margin-bottom: 5px; | |
- } | |
- | |
- .filename { | |
- font-style: italic; | |
- } | |
- </style> | |
- <script type="text/javascript"> | |
- function about() { | |
- info = document.getElementById('about-content'); | |
- if (window.XMLHttpRequest) | |
- { xhr = new XMLHttpRequest(); } | |
- else | |
- { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } | |
- xhr.open("GET","rails/info/properties",false); | |
- xhr.send(""); | |
- info.innerHTML = xhr.responseText; | |
- info.style.display = 'block' | |
- } | |
- </script> | |
- </head> | |
- <body> | |
- <div id="page"> | |
- <div id="sidebar"> | |
- <ul id="sidebar-items"> | |
- <li> | |
- <h3>Browse the documentation</h3> | |
- <ul class="links"> | |
- <li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li> | |
- <li><a href="http://api.rubyonrails.org/">Rails API</a></li> | |
- <li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li> | |
- <li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li> | |
- </ul> | |
- </li> | |
- </ul> | |
- </div> | |
- | |
- <div id="content"> | |
- <div id="header"> | |
- <h1>Welcome aboard</h1> | |
- <h2>You’re riding Ruby on Rails!</h2> | |
- </div> | |
- | |
- <div id="about"> | |
- <h3><a href="rails/info/properties" onclick="about(); return false">About your application’s environment</a></h3> | |
- <div id="about-content" style="display: none"></div> | |
- </div> | |
- | |
- <div id="getting-started"> | |
- <h1>Getting started</h1> | |
- <h2>Here’s how to get rolling:</h2> | |
- | |
- <ol> | |
- <li> | |
- <h2>Use <code>rails generate</code> to create your models and controllers</h2> | |
- <p>To see all available options, run it without parameters.</p> | |
- </li> | |
- | |
- <li> | |
- <h2>Set up a default route and remove <span class="filename">public/index.html</span></h2> | |
- <p>Routes are set up in <span class="filename">config/routes.rb</span>.</p> | |
- </li> | |
- | |
- <li> | |
- <h2>Create your database</h2> | |
- <p>Run <code>rake db:create</code> to create your database. If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p> | |
- </li> | |
- </ol> | |
- </div> | |
- </div> | |
- | |
- <div id="footer"> </div> | |
- </div> | |
- </body> | |
-</html> | |
diff --git a/script/cucumber b/script/cucumber | |
new file mode 100755 | |
index 0000000..7fa5c92 | |
--- /dev/null | |
+++ b/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 | |
diff --git a/spec/controllers/content_controller_spec.rb b/spec/controllers/content_controller_spec.rb | |
new file mode 100644 | |
index 0000000..fd61b61 | |
--- /dev/null | |
+++ b/spec/controllers/content_controller_spec.rb | |
@@ -0,0 +1,26 @@ | |
+require 'spec_helper' | |
+ | |
+describe ContentController do | |
+ | |
+ describe "GET 'silver'" do | |
+ it "returns http success" do | |
+ get 'silver' | |
+ response.should be_success | |
+ end | |
+ end | |
+ | |
+ describe "GET 'gold'" do | |
+ it "returns http success" do | |
+ get 'gold' | |
+ response.should be_success | |
+ end | |
+ end | |
+ | |
+ describe "GET 'platinum'" do | |
+ it "returns http success" do | |
+ get 'platinum' | |
+ response.should be_success | |
+ end | |
+ end | |
+ | |
+end | |
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb | |
new file mode 100644 | |
index 0000000..8e8c5c3 | |
--- /dev/null | |
+++ b/spec/controllers/home_controller_spec.rb | |
@@ -0,0 +1,12 @@ | |
+require 'spec_helper' | |
+ | |
+describe HomeController do | |
+ | |
+ describe "GET 'index'" do | |
+ it "should be successful" do | |
+ get 'index' | |
+ response.should be_success | |
+ end | |
+ end | |
+ | |
+end | |
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb | |
new file mode 100644 | |
index 0000000..e73d91a | |
--- /dev/null | |
+++ b/spec/controllers/users_controller_spec.rb | |
@@ -0,0 +1,24 @@ | |
+require 'spec_helper' | |
+ | |
+describe UsersController do | |
+ | |
+ before (:each) do | |
+ @user = FactoryGirl.create(:user) | |
+ sign_in @user | |
+ end | |
+ | |
+ describe "GET 'show'" do | |
+ | |
+ it "should be successful" do | |
+ get :show, :id => @user.id | |
+ response.should be_success | |
+ end | |
+ | |
+ it "should find the right user" do | |
+ get :show, :id => @user.id | |
+ assigns(:user).should == @user | |
+ end | |
+ | |
+ end | |
+ | |
+end | |
diff --git a/spec/factories/users.rb b/spec/factories/users.rb | |
new file mode 100644 | |
index 0000000..30cd9aa | |
--- /dev/null | |
+++ b/spec/factories/users.rb | |
@@ -0,0 +1,12 @@ | |
+# Read about factories at https://github.com/thoughtbot/factory_girl | |
+ | |
+FactoryGirl.define do | |
+ factory :user do | |
+ name 'Test User' | |
+ email '[email protected]' | |
+ password 'please' | |
+ password_confirmation 'please' | |
+ # required if the Devise Confirmable module is used | |
+ # confirmed_at Time.now | |
+ end | |
+end | |
\ No newline at end of file | |
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb | |
new file mode 100644 | |
index 0000000..8d004b3 | |
--- /dev/null | |
+++ b/spec/mailers/user_mailer_spec.rb | |
@@ -0,0 +1,5 @@ | |
+require "spec_helper" | |
+ | |
+describe UserMailer do | |
+ pending "add some examples to (or delete) #{__FILE__}" | |
+end | |
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb | |
new file mode 100644 | |
index 0000000..4c96cdd | |
--- /dev/null | |
+++ b/spec/models/user_spec.rb | |
@@ -0,0 +1,103 @@ | |
+require 'spec_helper' | |
+ | |
+describe User do | |
+ | |
+ before(:each) do | |
+ @attr = { | |
+ :name => "Example User", | |
+ :email => "[email protected]", | |
+ :password => "foobar", | |
+ :password_confirmation => "foobar" | |
+ } | |
+ end | |
+ | |
+ it "should create a new instance given a valid attribute" do | |
+ User.create!(@attr) | |
+ end | |
+ | |
+ it "should require an email address" do | |
+ no_email_user = User.new(@attr.merge(:email => "")) | |
+ no_email_user.should_not be_valid | |
+ end | |
+ | |
+ it "should accept valid email addresses" do | |
+ addresses = %w[[email protected] [email protected] [email protected]] | |
+ addresses.each do |address| | |
+ valid_email_user = User.new(@attr.merge(:email => address)) | |
+ valid_email_user.should be_valid | |
+ end | |
+ end | |
+ | |
+ it "should reject invalid email addresses" do | |
+ addresses = %w[user@foo,com user_at_foo.org example.user@foo.] | |
+ addresses.each do |address| | |
+ invalid_email_user = User.new(@attr.merge(:email => address)) | |
+ invalid_email_user.should_not be_valid | |
+ end | |
+ end | |
+ | |
+ it "should reject duplicate email addresses" do | |
+ User.create!(@attr) | |
+ user_with_duplicate_email = User.new(@attr) | |
+ user_with_duplicate_email.should_not be_valid | |
+ end | |
+ | |
+ it "should reject email addresses identical up to case" do | |
+ upcased_email = @attr[:email].upcase | |
+ User.create!(@attr.merge(:email => upcased_email)) | |
+ user_with_duplicate_email = User.new(@attr) | |
+ user_with_duplicate_email.should_not be_valid | |
+ end | |
+ | |
+ describe "passwords" do | |
+ | |
+ before(:each) do | |
+ @user = User.new(@attr) | |
+ end | |
+ | |
+ it "should have a password attribute" do | |
+ @user.should respond_to(:password) | |
+ end | |
+ | |
+ it "should have a password confirmation attribute" do | |
+ @user.should respond_to(:password_confirmation) | |
+ end | |
+ end | |
+ | |
+ describe "password validations" do | |
+ | |
+ it "should require a password" do | |
+ User.new(@attr.merge(:password => "", :password_confirmation => "")). | |
+ should_not be_valid | |
+ end | |
+ | |
+ it "should require a matching password confirmation" do | |
+ User.new(@attr.merge(:password_confirmation => "invalid")). | |
+ should_not be_valid | |
+ end | |
+ | |
+ it "should reject short passwords" do | |
+ short = "a" * 5 | |
+ hash = @attr.merge(:password => short, :password_confirmation => short) | |
+ User.new(hash).should_not be_valid | |
+ end | |
+ | |
+ end | |
+ | |
+ describe "password encryption" do | |
+ | |
+ before(:each) do | |
+ @user = User.create!(@attr) | |
+ end | |
+ | |
+ it "should have an encrypted password attribute" do | |
+ @user.should respond_to(:encrypted_password) | |
+ end | |
+ | |
+ it "should set the encrypted password attribute" do | |
+ @user.encrypted_password.should_not be_blank | |
+ end | |
+ | |
+ end | |
+ | |
+end | |
\ No newline at end of file | |
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb | |
new file mode 100644 | |
index 0000000..280231a | |
--- /dev/null | |
+++ b/spec/spec_helper.rb | |
@@ -0,0 +1,51 @@ | |
+# This file is copied to spec/ when you run 'rails generate rspec:install' | |
+ENV["RAILS_ENV"] ||= 'test' | |
+require File.expand_path("../../config/environment", __FILE__) | |
+require 'rspec/rails' | |
+require 'email_spec' | |
+require 'rspec/autorun' | |
+ | |
+# Requires supporting ruby files with custom matchers and macros, etc, | |
+# in spec/support/ and its subdirectories. | |
+Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} | |
+ | |
+RSpec.configure do |config| | |
+ config.include(EmailSpec::Helpers) | |
+ config.include(EmailSpec::Matchers) | |
+ # ## Mock Framework | |
+ # | |
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: | |
+ # | |
+ # config.mock_with :mocha | |
+ # config.mock_with :flexmock | |
+ # config.mock_with :rr | |
+ | |
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures | |
+ config.fixture_path = "#{::Rails.root}/spec/fixtures" | |
+ | |
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your | |
+ # examples within a transaction, remove the following line or assign false | |
+ # instead of true. | |
+ config.use_transactional_fixtures = true | |
+ | |
+ # If true, the base class of anonymous controllers will be inferred | |
+ # automatically. This will be the default behavior in future versions of | |
+ # rspec-rails. | |
+ config.infer_base_class_for_anonymous_controllers = false | |
+ | |
+ # Run specs in random order to surface order dependencies. If you find an | |
+ # order dependency and want to debug it, you can fix the order by providing | |
+ # the seed, which is printed after each run. | |
+ # --seed 1234 | |
+ config.order = "random" | |
+ | |
+ config.before(:suite) do | |
+ DatabaseCleaner.strategy = :truncation | |
+ end | |
+ config.before(:each) do | |
+ DatabaseCleaner.start | |
+ end | |
+ config.after(:each) do | |
+ DatabaseCleaner.clean | |
+ end | |
+end | |
diff --git a/spec/support/devise.rb b/spec/support/devise.rb | |
new file mode 100644 | |
index 0000000..3552bea | |
--- /dev/null | |
+++ b/spec/support/devise.rb | |
@@ -0,0 +1,3 @@ | |
+RSpec.configure do |config| | |
+ config.include Devise::TestHelpers, :type => :controller | |
+end | |
diff --git a/test/fixtures/.gitkeep b/test/fixtures/.gitkeep | |
deleted file mode 100644 | |
index e69de29..0000000 | |
diff --git a/test/functional/.gitkeep b/test/functional/.gitkeep | |
deleted file mode 100644 | |
index e69de29..0000000 | |
diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep | |
deleted file mode 100644 | |
index e69de29..0000000 | |
diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb | |
deleted file mode 100644 | |
index 3fea27b..0000000 | |
--- a/test/performance/browsing_test.rb | |
+++ /dev/null | |
@@ -1,12 +0,0 @@ | |
-require 'test_helper' | |
-require 'rails/performance_test_help' | |
- | |
-class BrowsingTest < ActionDispatch::PerformanceTest | |
- # Refer to the documentation for all available options | |
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] | |
- # :output => 'tmp/performance', :formats => [:flat] } | |
- | |
- def test_homepage | |
- get '/' | |
- end | |
-end | |
diff --git a/test/test_helper.rb b/test/test_helper.rb | |
deleted file mode 100644 | |
index 8bf1192..0000000 | |
--- a/test/test_helper.rb | |
+++ /dev/null | |
@@ -1,13 +0,0 @@ | |
-ENV["RAILS_ENV"] = "test" | |
-require File.expand_path('../../config/environment', __FILE__) | |
-require 'rails/test_help' | |
- | |
-class ActiveSupport::TestCase | |
- # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. | |
- # | |
- # Note: You'll currently still have to declare fixtures explicitly in integration tests | |
- # -- they do not yet inherit this setting | |
- fixtures :all | |
- | |
- # Add more helper methods to be used by all tests here... | |
-end | |
diff --git a/test/unit/.gitkeep b/test/unit/.gitkeep | |
deleted file mode 100644 | |
index e69de29..0000000 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment