├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── rails.png │ ├── javascripts │ │ ├── application.js │ │ ├── sessions.js.coffee │ │ ├── static_pages.js.coffee │ │ └── users.js.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── custom.css.scss │ │ ├── sessions.css.scss │ │ ├── static_pages.css.scss │ │ └── users.css.scss ├── controllers │ ├── application_controller.rb │ ├── microposts_controller.rb │ ├── relationships_controller.rb │ ├── sessions_controller.rb │ ├── static_pages_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ ├── sessions_helper.rb │ ├── static_pages_helper.rb │ └── users_helper.rb ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── micropost.rb │ ├── relationship.rb │ └── user.rb └── views │ ├── layouts │ ├── _footer.html.erb │ ├── _header.html.erb │ ├── _shim.html.erb │ └── application.html.erb │ ├── microposts │ └── _micropost.html.erb │ ├── relationships │ ├── create.js.erb │ └── destroy.js.erb │ ├── sessions │ └── new.html.erb │ ├── shared │ ├── _error_messages.html.erb │ ├── _feed.html.erb │ ├── _feed_item.html.erb │ ├── _micropost_form.html.erb │ ├── _stats.html.erb │ └── _user_info.html.erb │ ├── static_pages │ ├── about.html.erb │ ├── contact.html.erb │ ├── help.html.erb │ └── home.html.erb │ └── users │ ├── _follow.html.erb │ ├── _follow_form.html.erb │ ├── _unfollow.html.erb │ ├── _user.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ ├── show.html.erb │ └── show_follow.html.erb ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cucumber.yml ├── database.yml.example ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20120308032820_create_users.rb │ ├── 20120308034224_add_index_to_users_email.rb │ ├── 20120308034454_add_password_digest_to_users.rb │ ├── 20120308054414_add_remember_token_to_users.rb │ ├── 20120308193644_add_admin_to_users.rb │ ├── 20120308210452_create_microposts.rb │ └── 20120308215846_create_relationships.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── features ├── signing_in.feature ├── step_definitions │ └── authentication_steps.rb └── support │ └── env.rb ├── lib ├── assets │ └── .gitkeep └── tasks │ ├── .gitkeep │ ├── cucumber.rake │ └── sample_data.rake ├── log └── .gitkeep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt ├── script ├── cucumber └── rails ├── spec ├── controllers │ └── relationships_controller_spec.rb ├── factories.rb ├── helpers │ └── application_helper_spec.rb ├── models │ ├── micropost_spec.rb │ ├── relationship_spec.rb │ └── user_spec.rb ├── requests │ ├── authentication_pages_spec.rb │ ├── micropost_pages_spec.rb │ ├── static_pages_spec.rb │ └── user_pages_spec.rb ├── spec_helper.rb └── support │ └── utilities.rb └── vendor ├── assets ├── javascripts │ └── .gitkeep └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/*.log 15 | /tmp 16 | bundler_stubs/ 17 | 18 | # Ignore other unneeded files. 19 | database.yml 20 | doc/ 21 | *.swp 22 | *~ 23 | .project 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --drb 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '3.2.16' 4 | gem 'bootstrap-sass', '2.1' 5 | gem 'bcrypt-ruby', '3.0.1' 6 | gem 'faker', '1.0.1' 7 | gem 'will_paginate', '3.0.3' 8 | gem 'bootstrap-will_paginate', '0.0.6' 9 | gem 'jquery-rails', '2.0.2' 10 | 11 | group :development, :test do 12 | gem 'sqlite3', '1.3.5' 13 | gem 'rspec-rails', '2.11.0' 14 | gem 'guard-rspec', '1.2.1' 15 | gem 'guard-spork', '1.2.0' 16 | gem 'childprocess', '0.3.6' 17 | gem 'spork', '0.9.2' 18 | end 19 | 20 | # Gems used only for assets and not required 21 | # in production environments by default. 22 | group :assets do 23 | gem 'sass-rails', '3.2.5' 24 | gem 'coffee-rails', '3.2.2' 25 | gem 'uglifier', '1.2.3' 26 | end 27 | 28 | group :test do 29 | gem 'capybara', '1.1.2' 30 | gem 'factory_girl_rails', '4.1.0' 31 | gem 'cucumber-rails', '1.2.1', :require => false 32 | gem 'database_cleaner', '0.7.0' 33 | # gem 'launchy', '2.1.0' 34 | # gem 'rb-fsevent', '0.9.1', :require => false 35 | # gem 'growl', '1.0.3' 36 | end 37 | 38 | group :production do 39 | gem 'pg', '0.12.2' 40 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (3.2.16) 5 | actionpack (= 3.2.16) 6 | mail (~> 2.5.4) 7 | actionpack (3.2.16) 8 | activemodel (= 3.2.16) 9 | activesupport (= 3.2.16) 10 | builder (~> 3.0.0) 11 | erubis (~> 2.7.0) 12 | journey (~> 1.0.4) 13 | rack (~> 1.4.5) 14 | rack-cache (~> 1.2) 15 | rack-test (~> 0.6.1) 16 | sprockets (~> 2.2.1) 17 | activemodel (3.2.16) 18 | activesupport (= 3.2.16) 19 | builder (~> 3.0.0) 20 | activerecord (3.2.16) 21 | activemodel (= 3.2.16) 22 | activesupport (= 3.2.16) 23 | arel (~> 3.0.2) 24 | tzinfo (~> 0.3.29) 25 | activeresource (3.2.16) 26 | activemodel (= 3.2.16) 27 | activesupport (= 3.2.16) 28 | activesupport (3.2.16) 29 | i18n (~> 0.6, >= 0.6.4) 30 | multi_json (~> 1.0) 31 | arel (3.0.3) 32 | bcrypt-ruby (3.0.1) 33 | bootstrap-sass (2.1.0.0) 34 | bootstrap-will_paginate (0.0.6) 35 | will_paginate 36 | builder (3.0.4) 37 | capybara (1.1.2) 38 | mime-types (>= 1.16) 39 | nokogiri (>= 1.3.3) 40 | rack (>= 1.0.0) 41 | rack-test (>= 0.5.4) 42 | selenium-webdriver (~> 2.0) 43 | xpath (~> 0.1.4) 44 | celluloid (0.15.2) 45 | timers (~> 1.1.0) 46 | childprocess (0.3.6) 47 | ffi (~> 1.0, >= 1.0.6) 48 | coderay (1.1.0) 49 | coffee-rails (3.2.2) 50 | coffee-script (>= 2.2.0) 51 | railties (~> 3.2.0) 52 | coffee-script (2.2.0) 53 | coffee-script-source 54 | execjs 55 | coffee-script-source (1.6.3) 56 | cucumber (1.3.10) 57 | builder (>= 2.1.2) 58 | diff-lcs (>= 1.1.3) 59 | gherkin (~> 2.12) 60 | multi_json (>= 1.7.5, < 2.0) 61 | multi_test (>= 0.0.2) 62 | cucumber-rails (1.2.1) 63 | capybara (>= 1.1.2) 64 | cucumber (>= 1.1.3) 65 | nokogiri (>= 1.5.0) 66 | database_cleaner (0.7.0) 67 | diff-lcs (1.1.3) 68 | erubis (2.7.0) 69 | execjs (2.0.2) 70 | factory_girl (4.1.0) 71 | activesupport (>= 3.0.0) 72 | factory_girl_rails (4.1.0) 73 | factory_girl (~> 4.1.0) 74 | railties (>= 3.0.0) 75 | faker (1.0.1) 76 | i18n (~> 0.4) 77 | ffi (1.9.3) 78 | formatador (0.2.4) 79 | gherkin (2.12.2) 80 | multi_json (~> 1.3) 81 | guard (2.2.4) 82 | formatador (>= 0.2.4) 83 | listen (~> 2.1) 84 | lumberjack (~> 1.0) 85 | pry (>= 0.9.12) 86 | thor (>= 0.18.1) 87 | guard-rspec (1.2.1) 88 | guard (>= 1.1) 89 | guard-spork (1.2.0) 90 | childprocess 91 | guard (>= 1.1) 92 | spork (>= 0.8.4) 93 | sys-proctable 94 | hike (1.2.3) 95 | i18n (0.6.9) 96 | journey (1.0.4) 97 | jquery-rails (2.0.2) 98 | railties (>= 3.2.0, < 5.0) 99 | thor (~> 0.14) 100 | json (1.8.1) 101 | listen (2.3.1) 102 | celluloid (>= 0.15.2) 103 | rb-fsevent (>= 0.9.3) 104 | rb-inotify (>= 0.9) 105 | lumberjack (1.0.4) 106 | mail (2.5.4) 107 | mime-types (~> 1.16) 108 | treetop (~> 1.4.8) 109 | method_source (0.8.2) 110 | mime-types (1.25.1) 111 | mini_portile (0.5.2) 112 | multi_json (1.8.2) 113 | multi_test (0.0.2) 114 | nokogiri (1.6.0) 115 | mini_portile (~> 0.5.0) 116 | pg (0.12.2) 117 | polyglot (0.3.3) 118 | pry (0.9.12.4) 119 | coderay (~> 1.0) 120 | method_source (~> 0.8) 121 | slop (~> 3.4) 122 | rack (1.4.5) 123 | rack-cache (1.2) 124 | rack (>= 0.4) 125 | rack-ssl (1.3.3) 126 | rack 127 | rack-test (0.6.2) 128 | rack (>= 1.0) 129 | rails (3.2.16) 130 | actionmailer (= 3.2.16) 131 | actionpack (= 3.2.16) 132 | activerecord (= 3.2.16) 133 | activeresource (= 3.2.16) 134 | activesupport (= 3.2.16) 135 | bundler (~> 1.0) 136 | railties (= 3.2.16) 137 | railties (3.2.16) 138 | actionpack (= 3.2.16) 139 | activesupport (= 3.2.16) 140 | rack-ssl (~> 1.3.2) 141 | rake (>= 0.8.7) 142 | rdoc (~> 3.4) 143 | thor (>= 0.14.6, < 2.0) 144 | rake (10.1.0) 145 | rb-fsevent (0.9.3) 146 | rb-inotify (0.9.2) 147 | ffi (>= 0.5.0) 148 | rdoc (3.12.2) 149 | json (~> 1.4) 150 | rspec (2.11.0) 151 | rspec-core (~> 2.11.0) 152 | rspec-expectations (~> 2.11.0) 153 | rspec-mocks (~> 2.11.0) 154 | rspec-core (2.11.1) 155 | rspec-expectations (2.11.3) 156 | diff-lcs (~> 1.1.3) 157 | rspec-mocks (2.11.3) 158 | rspec-rails (2.11.0) 159 | actionpack (>= 3.0) 160 | activesupport (>= 3.0) 161 | railties (>= 3.0) 162 | rspec (~> 2.11.0) 163 | rubyzip (1.0.0) 164 | sass (3.2.12) 165 | sass-rails (3.2.5) 166 | railties (~> 3.2.0) 167 | sass (>= 3.1.10) 168 | tilt (~> 1.3) 169 | selenium-webdriver (2.37.0) 170 | childprocess (>= 0.2.5) 171 | multi_json (~> 1.0) 172 | rubyzip (~> 1.0.0) 173 | websocket (~> 1.0.4) 174 | slop (3.4.7) 175 | spork (0.9.2) 176 | sprockets (2.2.2) 177 | hike (~> 1.2) 178 | multi_json (~> 1.0) 179 | rack (~> 1.0) 180 | tilt (~> 1.1, != 1.3.0) 181 | sqlite3 (1.3.5) 182 | sys-proctable (0.9.3) 183 | thor (0.18.1) 184 | tilt (1.4.1) 185 | timers (1.1.0) 186 | treetop (1.4.15) 187 | polyglot 188 | polyglot (>= 0.3.1) 189 | tzinfo (0.3.38) 190 | uglifier (1.2.3) 191 | execjs (>= 0.3.0) 192 | multi_json (>= 1.0.2) 193 | websocket (1.0.7) 194 | will_paginate (3.0.3) 195 | xpath (0.1.4) 196 | nokogiri (~> 1.3) 197 | 198 | PLATFORMS 199 | ruby 200 | 201 | DEPENDENCIES 202 | bcrypt-ruby (= 3.0.1) 203 | bootstrap-sass (= 2.1) 204 | bootstrap-will_paginate (= 0.0.6) 205 | capybara (= 1.1.2) 206 | childprocess (= 0.3.6) 207 | coffee-rails (= 3.2.2) 208 | cucumber-rails (= 1.2.1) 209 | database_cleaner (= 0.7.0) 210 | factory_girl_rails (= 4.1.0) 211 | faker (= 1.0.1) 212 | guard-rspec (= 1.2.1) 213 | guard-spork (= 1.2.0) 214 | jquery-rails (= 2.0.2) 215 | pg (= 0.12.2) 216 | rails (= 3.2.16) 217 | rspec-rails (= 2.11.0) 218 | sass-rails (= 3.2.5) 219 | spork (= 0.9.2) 220 | sqlite3 (= 1.3.5) 221 | uglifier (= 1.2.3) 222 | will_paginate (= 3.0.3) 223 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | require 'active_support/core_ext' 5 | 6 | guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do 7 | watch('config/application.rb') 8 | watch('config/environment.rb') 9 | watch(%r{^config/environments/.+\.rb$}) 10 | watch(%r{^config/initializers/.+\.rb$}) 11 | watch('Gemfile') 12 | watch('Gemfile.lock') 13 | watch('spec/spec_helper.rb') 14 | watch('test/test_helper.rb') 15 | watch('spec/support/') 16 | end 17 | 18 | guard 'rspec', :version => 2, :all_after_pass => false, :cli => '--drb' do 19 | watch(%r{^spec/.+_spec\.rb$}) 20 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 21 | watch('spec/spec_helper.rb') { "spec" } 22 | 23 | # Rails example 24 | watch(%r{^spec/.+_spec\.rb$}) 25 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 26 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 27 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 28 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 29 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 30 | watch('spec/spec_helper.rb') { "spec" } 31 | watch('config/routes.rb') { "spec/routing" } 32 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 33 | # Capybara request specs 34 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } 35 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m| 36 | ["spec/routing/#{m[1]}_routing_spec.rb", 37 | "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", 38 | "spec/acceptance/#{m[1]}_spec.rb", 39 | "spec/requests/#{m[1].singularize}_pages_spec.rb", 40 | (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" : 41 | "spec/requests/#{m[1].singularize}_pages_spec.rb")] 42 | end 43 | watch(%r{^app/views/(.+)/}) do |m| 44 | "spec/requests/#{m[1].singularize}_pages_spec.rb" 45 | end 46 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Ruby on Rails Tutorial source code is licensed jointly 2 | under the MIT License and the Beerware License. 3 | 4 | The MIT License 5 | 6 | Copyright (c) 2012 Michael Hartl 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | /* 27 | * ---------------------------------------------------------------------------- 28 | * "THE BEER-WARE LICENSE" (Revision 42): 29 | * Michael Hartl wrote this code. As long as you retain this notice you 30 | * can do whatever you want with this stuff. If we meet some day, and you think 31 | * this stuff is worth it, you can buy me a beer in return. 32 | * ---------------------------------------------------------------------------- 33 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby on Rails Tutorial: sample application 2 | 3 | **This repository is out of date and is no longer maintained. Please see the [Rails Tutorial Help page](http://railstutorial.org/help) for the most up-to-date version.** 4 | 5 | 6 | This is the sample application for 7 | [*Ruby on Rails Tutorial: Learn Web Development with Rails*](http://railstutorial.org/) 8 | by [Michael Hartl](http://michaelhartl.com/). You can use this reference implementation to help track down errors if you end up having trouble with code in the tutorial. In particular, as a first debugging check I suggest getting the test suite to pass on your local machine: 9 | 10 | $ cd /tmp 11 | $ git clone git@github.com:railstutorial/sample_app_2nd_ed.git 12 | $ cd sample_app_2nd_ed 13 | $ cp config/database.yml.example config/database.yml 14 | $ bundle install 15 | $ bundle exec rake db:migrate 16 | $ bundle exec rake db:test:prepare 17 | $ bundle exec rspec spec/ 18 | 19 | If the tests don't pass, it means there may be something wrong with your system. If they do pass, then you can debug your code by comparing it with the reference implementation. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | SampleApp::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/sessions.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/static_pages.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/custom.css.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | 3 | /* mixins, variables, etc. */ 4 | 5 | $grayMediumLight: #eaeaea; 6 | 7 | @mixin box_sizing { 8 | -moz-box-sizing: border-box; 9 | -webkit-box-sizing: border-box; 10 | box-sizing: border-box; 11 | } 12 | 13 | /* universal */ 14 | 15 | html { 16 | overflow-y: scroll; 17 | } 18 | 19 | body { 20 | padding-top: 60px; 21 | } 22 | 23 | section { 24 | overflow: auto; 25 | } 26 | 27 | textarea { 28 | resize: vertical; 29 | } 30 | 31 | .center { 32 | text-align: center; 33 | h1 { 34 | margin-bottom: 10px; 35 | } 36 | } 37 | 38 | /* typography */ 39 | 40 | h1, h2, h3, h4, h5, h6 { 41 | line-height: 1; 42 | } 43 | 44 | h1 { 45 | font-size: 3em; 46 | letter-spacing: -2px; 47 | margin-bottom: 30px; 48 | text-align: center; 49 | } 50 | 51 | h2 { 52 | font-size: 1.7em; 53 | letter-spacing: -1px; 54 | margin-bottom: 30px; 55 | text-align: center; 56 | font-weight: normal; 57 | color: $grayLight; 58 | } 59 | 60 | p { 61 | font-size: 1.1em; 62 | line-height: 1.7em; 63 | } 64 | 65 | 66 | /* header */ 67 | 68 | #logo { 69 | float: left; 70 | margin-right: 10px; 71 | font-size: 1.7em; 72 | color: white; 73 | text-transform: uppercase; 74 | letter-spacing: -1px; 75 | padding-top: 9px; 76 | font-weight: bold; 77 | line-height: 1; 78 | &:hover { 79 | color: white; 80 | text-decoration: none; 81 | } 82 | } 83 | 84 | /* footer */ 85 | 86 | footer { 87 | margin-top: 45px; 88 | padding-top: 5px; 89 | border-top: 1px solid $grayMediumLight; 90 | color: $grayLight; 91 | a { 92 | color: $gray; 93 | &:hover { 94 | color: $grayDarker; 95 | } 96 | } 97 | small { 98 | float: left; 99 | } 100 | ul { 101 | float: right; 102 | list-style: none; 103 | li { 104 | float: left; 105 | margin-left: 10px; 106 | } 107 | } 108 | } 109 | 110 | /* miscellaneous */ 111 | 112 | .debug_dump { 113 | clear: both; 114 | float: left; 115 | width: 100%; 116 | margin-top: 45px; 117 | @include box_sizing; 118 | } 119 | 120 | /* sidebar */ 121 | 122 | aside { 123 | section { 124 | padding: 10px 0; 125 | border-top: 1px solid $grayLighter; 126 | &:first-child { 127 | border: 0; 128 | padding-top: 0; 129 | } 130 | span { 131 | display: block; 132 | margin-bottom: 3px; 133 | line-height: 1; 134 | } 135 | h1 { 136 | font-size: 1.4em; 137 | text-align: left; 138 | letter-spacing: -1px; 139 | margin-bottom: 3px; 140 | margin-top: 0px; 141 | } 142 | } 143 | } 144 | 145 | .gravatar { 146 | float: left; 147 | margin-right: 10px; 148 | } 149 | 150 | .stats { 151 | overflow: auto; 152 | a { 153 | float: left; 154 | padding: 0 10px; 155 | border-left: 1px solid $grayLighter; 156 | color: gray; 157 | &:first-child { 158 | padding-left: 0; 159 | border: 0; 160 | } 161 | &:hover { 162 | text-decoration: none; 163 | color: $blue; 164 | } 165 | } 166 | strong { 167 | display: block; 168 | } 169 | } 170 | 171 | .user_avatars { 172 | overflow: auto; 173 | margin-top: 10px; 174 | .gravatar { 175 | margin: 1px 1px; 176 | } 177 | } 178 | 179 | /* forms */ 180 | 181 | input, textarea, select, .uneditable-input { 182 | border: 1px solid #bbb; 183 | width: 100%; 184 | padding: 10px; 185 | margin-bottom: 15px; 186 | @include box_sizing; 187 | } 188 | 189 | input { 190 | height: auto !important; 191 | } 192 | 193 | #error_explanation { 194 | color:#f00; 195 | ul { 196 | list-style: none; 197 | margin: 0 0 18px 0; 198 | } 199 | } 200 | 201 | .field_with_errors { 202 | @extend .control-group; 203 | @extend .error; 204 | } 205 | 206 | /* users index */ 207 | 208 | .users { 209 | list-style: none; 210 | margin: 0; 211 | li { 212 | overflow: auto; 213 | padding: 10px 0; 214 | border-top: 1px solid $grayLighter; 215 | &:last-child { 216 | border-bottom: 1px solid $grayLighter; 217 | } 218 | } 219 | } 220 | 221 | /* microposts */ 222 | 223 | .microposts { 224 | list-style: none; 225 | margin: 10px 0 0 0; 226 | 227 | li { 228 | padding: 10px 0; 229 | border-top: 1px solid #e8e8e8; 230 | } 231 | } 232 | 233 | .content { 234 | display: block; 235 | } 236 | 237 | .timestamp { 238 | color: $grayLight; 239 | } 240 | 241 | aside { 242 | textarea { 243 | height: 100px; 244 | margin-bottom: 5px; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /app/assets/stylesheets/sessions.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Sessions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static_pages.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the StaticPages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | include SessionsHelper 4 | 5 | # Force signout to prevent CSRF attacks 6 | def handle_unverified_request 7 | sign_out 8 | super 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/microposts_controller.rb: -------------------------------------------------------------------------------- 1 | class MicropostsController < ApplicationController 2 | before_filter :signed_in_user 3 | before_filter :correct_user, only: :destroy 4 | 5 | def create 6 | @micropost = current_user.microposts.build(params[:micropost]) 7 | if @micropost.save 8 | flash[:success] = "Micropost created!" 9 | redirect_to root_url 10 | else 11 | @feed_items = [] 12 | render 'static_pages/home' 13 | end 14 | end 15 | 16 | def destroy 17 | @micropost.destroy 18 | redirect_to root_url 19 | end 20 | 21 | private 22 | 23 | def correct_user 24 | @micropost = current_user.microposts.find_by_id(params[:id]) 25 | redirect_to root_url if @micropost.nil? 26 | end 27 | end -------------------------------------------------------------------------------- /app/controllers/relationships_controller.rb: -------------------------------------------------------------------------------- 1 | class RelationshipsController < ApplicationController 2 | before_filter :signed_in_user 3 | 4 | respond_to :html, :js 5 | 6 | def create 7 | @user = User.find(params[:relationship][:followed_id]) 8 | current_user.follow!(@user) 9 | respond_with @user 10 | end 11 | 12 | def destroy 13 | @user = Relationship.find(params[:id]).followed 14 | current_user.unfollow!(@user) 15 | respond_with @user 16 | end 17 | end -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | 3 | def new 4 | end 5 | 6 | def create 7 | user = User.find_by_email(params[:session][:email].downcase) 8 | if user && user.authenticate(params[:session][:password]) 9 | sign_in user 10 | redirect_back_or user 11 | else 12 | flash.now[:error] = 'Invalid email/password combination' 13 | render 'new' 14 | end 15 | end 16 | 17 | def destroy 18 | sign_out 19 | redirect_to root_url 20 | end 21 | end -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | 3 | def home 4 | if signed_in? 5 | @micropost = current_user.microposts.build 6 | @feed_items = current_user.feed.paginate(page: params[:page]) 7 | end 8 | end 9 | 10 | def help 11 | end 12 | 13 | def about 14 | end 15 | 16 | def contact 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_filter :signed_in_user, 3 | only: [:index, :edit, :update, :destroy, :following, :followers] 4 | before_filter :correct_user, only: [:edit, :update] 5 | before_filter :admin_user, only: :destroy 6 | 7 | def index 8 | @users = User.paginate(page: params[:page]) 9 | end 10 | 11 | def show 12 | @user = User.find(params[:id]) 13 | @microposts = @user.microposts.paginate(page: params[:page]) 14 | end 15 | 16 | def new 17 | @user = User.new 18 | end 19 | 20 | def create 21 | @user = User.new(params[:user]) 22 | if @user.save 23 | sign_in @user 24 | flash[:success] = "Welcome to the Sample App!" 25 | redirect_to @user 26 | else 27 | render 'new' 28 | end 29 | end 30 | 31 | def edit 32 | end 33 | 34 | def update 35 | if @user.update_attributes(params[:user]) 36 | flash[:success] = "Profile updated" 37 | sign_in @user 38 | redirect_to @user 39 | else 40 | render 'edit' 41 | end 42 | end 43 | 44 | def destroy 45 | User.find(params[:id]).destroy 46 | flash[:success] = "User destroyed." 47 | redirect_to users_url 48 | end 49 | 50 | def following 51 | @title = "Following" 52 | @user = User.find(params[:id]) 53 | @users = @user.followed_users.paginate(page: params[:page]) 54 | render 'show_follow' 55 | end 56 | 57 | def followers 58 | @title = "Followers" 59 | @user = User.find(params[:id]) 60 | @users = @user.followers.paginate(page: params[:page]) 61 | render 'show_follow' 62 | end 63 | 64 | private 65 | 66 | def correct_user 67 | @user = User.find(params[:id]) 68 | redirect_to(root_url) unless current_user?(@user) 69 | end 70 | 71 | def admin_user 72 | redirect_to(root_url) unless current_user.admin? 73 | end 74 | end -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | # Returns the full title on a per-page basis. 4 | def full_title(page_title) 5 | base_title = "Ruby on Rails Tutorial Sample App" 6 | if page_title.empty? 7 | base_title 8 | else 9 | "#{base_title} | #{page_title}" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | 3 | def sign_in(user) 4 | cookies.permanent[:remember_token] = user.remember_token 5 | self.current_user = user 6 | end 7 | 8 | def signed_in? 9 | !current_user.nil? 10 | end 11 | 12 | def current_user=(user) 13 | @current_user = user 14 | end 15 | 16 | def current_user 17 | @current_user ||= User.find_by_remember_token(cookies[:remember_token]) 18 | end 19 | 20 | def current_user?(user) 21 | user == current_user 22 | end 23 | 24 | def signed_in_user 25 | unless signed_in? 26 | store_location 27 | redirect_to signin_url, notice: "Please sign in." 28 | end 29 | end 30 | 31 | def sign_out 32 | current_user = nil 33 | cookies.delete(:remember_token) 34 | end 35 | 36 | def redirect_back_or(default) 37 | redirect_to(session[:return_to] || default) 38 | session.delete(:return_to) 39 | end 40 | 41 | def store_location 42 | session[:return_to] = request.url 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/helpers/static_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module StaticPagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | 3 | # Returns the Gravatar (http://gravatar.com/) for the given user. 4 | def gravatar_for(user, options = { size: 50 }) 5 | gravatar_id = Digest::MD5::hexdigest(user.email.downcase) 6 | size = options[:size] 7 | gravatar = 8 | gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" 9 | image_tag(gravatar_url, alt: user.name, class: "gravatar") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/micropost.rb: -------------------------------------------------------------------------------- 1 | class Micropost < ActiveRecord::Base 2 | attr_accessible :content 3 | belongs_to :user 4 | 5 | validates :user_id, presence: true 6 | validates :content, presence: true, length: { maximum: 140 } 7 | 8 | default_scope order: 'microposts.created_at DESC' 9 | 10 | def self.from_users_followed_by(user) 11 | followed_user_ids = "SELECT followed_id FROM relationships 12 | WHERE follower_id = :user_id" 13 | where("user_id IN (#{followed_user_ids}) OR user_id = :user_id", 14 | user_id: user.id) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/relationship.rb: -------------------------------------------------------------------------------- 1 | class Relationship < ActiveRecord::Base 2 | attr_accessible :followed_id 3 | 4 | belongs_to :follower, class_name: "User" 5 | belongs_to :followed, class_name: "User" 6 | 7 | validates :follower_id, presence: true 8 | validates :followed_id, presence: true 9 | end 10 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | attr_accessible :name, :email, :password, :password_confirmation 3 | has_secure_password 4 | has_many :microposts, dependent: :destroy 5 | has_many :relationships, foreign_key: "follower_id", dependent: :destroy 6 | has_many :followed_users, through: :relationships, source: :followed 7 | has_many :reverse_relationships, foreign_key: "followed_id", 8 | class_name: "Relationship", 9 | dependent: :destroy 10 | has_many :followers, through: :reverse_relationships, source: :follower 11 | 12 | before_save { email.downcase! } 13 | before_save :create_remember_token 14 | 15 | validates :name, presence: true, length: { maximum: 50 } 16 | VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i 17 | validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, 18 | uniqueness: { case_sensitive: false } 19 | validates :password, presence: true, length: { minimum: 6 } 20 | validates :password_confirmation, presence: true 21 | after_validation { self.errors.messages.delete(:password_digest) } 22 | 23 | def feed 24 | Micropost.from_users_followed_by(self) 25 | end 26 | 27 | def following?(other_user) 28 | relationships.find_by_followed_id(other_user.id) 29 | end 30 | 31 | def follow!(other_user) 32 | relationships.create!(followed_id: other_user.id) 33 | end 34 | 35 | def unfollow!(other_user) 36 | relationships.find_by_followed_id(other_user.id).destroy 37 | end 38 | 39 | private 40 | 41 | def create_remember_token 42 | self.remember_token = SecureRandom.urlsafe_base64 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/_header.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_shim.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= full_title(yield(:title)) %> 5 | <%= stylesheet_link_tag "application", media: "all" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | <%= render 'layouts/shim' %> 9 | 10 | 11 | <%= render 'layouts/header' %> 12 |
13 | <% flash.each do |key, value| %> 14 | <%= content_tag(:div, value, class: "alert alert-#{key}") %> 15 | <% end %> 16 | <%= yield %> 17 | <%= render 'layouts/footer' %> 18 | <%= debug(params) if Rails.env.development? %> 19 |
20 | 21 | -------------------------------------------------------------------------------- /app/views/microposts/_micropost.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= micropost.content %> 3 | 4 | Posted <%= time_ago_in_words(micropost.created_at) %> ago. 5 | 6 | <% if current_user?(micropost.user) %> 7 | <%= link_to "delete", micropost, method: :delete, 8 | data: { confirm: 'You sure?' }, 9 | title: micropost.content %> 10 | <% end %> 11 |
  • -------------------------------------------------------------------------------- /app/views/relationships/create.js.erb: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>") 2 | $("#followers").html('<%= @user.followers.count %>') -------------------------------------------------------------------------------- /app/views/relationships/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("<%= escape_javascript(render('users/follow')) %>") 2 | $("#followers").html('<%= @user.followers.count %>') -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, "Sign in") %> 2 |

    Sign in

    3 | 4 |
    5 |
    6 | <%= form_for(:session, url: sessions_path) do |f| %> 7 | 8 | <%= f.label :email %> 9 | <%= f.text_field :email %> 10 | 11 | <%= f.label :password %> 12 | <%= f.password_field :password %> 13 | 14 | <%= f.submit "Sign in", class: "btn btn-large btn-primary" %> 15 | <% end %> 16 | 17 |

    New user? <%= link_to "Sign up now!", signup_path %>

    18 |
    19 |
    -------------------------------------------------------------------------------- /app/views/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if object.errors.any? %> 2 |
    3 |
    4 | The form contains <%= pluralize(object.errors.count, "error") %>. 5 |
    6 | 11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/shared/_feed.html.erb: -------------------------------------------------------------------------------- 1 | <% if @feed_items.any? %> 2 |
      3 | <%= render partial: 'shared/feed_item', collection: @feed_items %> 4 |
    5 | <%= will_paginate @feed_items %> 6 | <% end %> -------------------------------------------------------------------------------- /app/views/shared/_feed_item.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to gravatar_for(feed_item.user), feed_item.user %> 3 | 4 | <%= link_to feed_item.user.name, feed_item.user %> 5 | 6 | <%= feed_item.content %> 7 | 8 | Posted <%= time_ago_in_words(feed_item.created_at) %> ago. 9 | 10 | <% if current_user?(feed_item.user) %> 11 | <%= link_to "delete", feed_item, method: :delete, 12 | data: { confirm: "You sure?" }, 13 | title: feed_item.content %> 14 | <% end %> 15 |
  • 16 | -------------------------------------------------------------------------------- /app/views/shared/_micropost_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@micropost) do |f| %> 2 | <%= render 'shared/error_messages', object: f.object %> 3 |
    4 | <%= f.text_area :content, placeholder: "Compose new micropost..." %> 5 |
    6 | <%= f.submit "Post", class: "btn btn-large btn-primary" %> 7 | <% end %> -------------------------------------------------------------------------------- /app/views/shared/_stats.html.erb: -------------------------------------------------------------------------------- 1 | <% @user ||= current_user %> 2 |
    3 | 4 | 5 | <%= @user.followed_users.count %> 6 | 7 | following 8 | 9 | 10 | 11 | <%= @user.followers.count %> 12 | 13 | followers 14 | 15 |
    -------------------------------------------------------------------------------- /app/views/shared/_user_info.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= gravatar_for current_user, size: 52 %> 3 | 4 |

    5 | <%= current_user.name %> 6 |

    7 | 8 | <%= link_to "view my profile", current_user %> 9 | 10 | 11 | <%= pluralize(current_user.microposts.count, "micropost") %> 12 | -------------------------------------------------------------------------------- /app/views/static_pages/about.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'About Us') %> 2 |

    About Us

    3 |

    4 | The Ruby on Rails Tutorial 5 | is a project to make a book and screencasts to teach web development 6 | with Ruby on Rails. This 7 | is the sample application for the tutorial. 8 |

    -------------------------------------------------------------------------------- /app/views/static_pages/contact.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'Contact') %> 2 |

    Contact

    3 |

    4 | Contact Ruby on Rails Tutorial about the sample app at the 5 | contact page. 6 |

    -------------------------------------------------------------------------------- /app/views/static_pages/help.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'Help') %> 2 |

    Help

    3 |

    4 | Get help on the Ruby on Rails Tutorial at the 5 | Rails Tutorial help page. 6 | To get help on this sample app, see the 7 | Rails Tutorial book. 8 |

    -------------------------------------------------------------------------------- /app/views/static_pages/home.html.erb: -------------------------------------------------------------------------------- 1 | <% if signed_in? %> 2 |
    3 | 14 |
    15 |

    Micropost Feed

    16 | <%= render 'shared/feed' %> 17 |
    18 |
    19 | <% else %> 20 |
    21 |

    Welcome to the Sample App

    22 | 23 |

    24 | This is the home page for the 25 | Ruby on Rails Tutorial 26 | sample application. 27 |

    28 | 29 | <%= link_to "Sign up now!", signup_path, 30 | class: "btn btn-large btn-primary" %> 31 |
    32 | 33 | <%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %> 34 | <% end %> -------------------------------------------------------------------------------- /app/views/users/_follow.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(current_user.relationships.build(followed_id: @user.id), 2 | remote: true) do |f| %> 3 |
    <%= f.hidden_field :followed_id %>
    4 | <%= f.submit "Follow", class: "btn btn-large btn-primary" %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/views/users/_follow_form.html.erb: -------------------------------------------------------------------------------- 1 | <% unless current_user?(@user) %> 2 |
    3 | <% if current_user.following?(@user) %> 4 | <%= render 'unfollow' %> 5 | <% else %> 6 | <%= render 'follow' %> 7 | <% end %> 8 |
    9 | <% end %> -------------------------------------------------------------------------------- /app/views/users/_unfollow.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(current_user.relationships.find_by_followed_id(@user), 2 | html: { method: :delete }, 3 | remote: true) do |f| %> 4 | <%= f.submit "Unfollow", class: "btn btn-large" %> 5 | <% end %> -------------------------------------------------------------------------------- /app/views/users/_user.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= gravatar_for user, size: 52 %> 3 | <%= link_to user.name, user %> 4 | <% if current_user.admin? && !current_user?(user) %> 5 | | <%= link_to "delete", user, method: :delete, 6 | data: { confirm: "You sure?" } %> 7 | <% end %> 8 |
  • -------------------------------------------------------------------------------- /app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, "Edit user") %> 2 |

    Update your profile

    3 | 4 |
    5 |
    6 | <%= form_for(@user) do |f| %> 7 | <%= render 'shared/error_messages', object: f.object %> 8 | 9 | <%= f.label :name %> 10 | <%= f.text_field :name %> 11 | 12 | <%= f.label :email %> 13 | <%= f.text_field :email %> 14 | 15 | <%= f.label :password %> 16 | <%= f.password_field :password %> 17 | 18 | <%= f.label :password_confirmation, "Confirm Password" %> 19 | <%= f.password_field :password_confirmation %> 20 | 21 | <%= f.submit "Save changes", class: "btn btn-large btn-primary" %> 22 | <% end %> 23 | 24 | <%= gravatar_for @user %> 25 | change 26 |
    27 |
    -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'All users') %> 2 |

    All users

    3 | 4 | <%= will_paginate %> 5 | 6 | 9 | 10 | <%= will_paginate %> -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'Sign up') %> 2 |

    Sign up

    3 | 4 |
    5 |
    6 | <%= form_for(@user) do |f| %> 7 | <%= render 'shared/error_messages', object: f.object %> 8 | <%= f.label :name %> 9 | <%= f.text_field :name %> 10 | 11 | <%= f.label :email %> 12 | <%= f.text_field :email %> 13 | 14 | <%= f.label :password %> 15 | <%= f.password_field :password %> 16 | 17 | <%= f.label :password_confirmation, "Confirmation" %> 18 | <%= f.password_field :password_confirmation %> 19 | 20 | <%= f.submit "Create my account", class: "btn btn-large btn-primary" %> 21 | <% end %> 22 |
    23 |
    -------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, @user.name) %> 2 |
    3 | 14 |
    15 | <%= render 'follow_form' if signed_in? %> 16 | <% if @user.microposts.any? %> 17 |

    Microposts (<%= @user.microposts.count %>)

    18 |
      19 | <%= render @microposts %> 20 |
    21 | <%= will_paginate @microposts %> 22 | <% end %> 23 |
    24 |
    -------------------------------------------------------------------------------- /app/views/users/show_follow.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, @title) %> 2 |
    3 | 21 |
    22 |

    <%= @title %>

    23 | <% if @users.any? %> 24 | 27 | <%= will_paginate %> 28 | <% end %> 29 |
    30 |
    -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run SampleApp::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "active_resource/railtie" 8 | require "sprockets/railtie" 9 | # require "rails/test_unit/railtie" 10 | 11 | if defined?(Bundler) 12 | # If you precompile assets before deploying to production, use this line 13 | Bundler.require(*Rails.groups(:assets => %w(development test))) 14 | # If you want your assets lazily compiled in production, use this line 15 | # Bundler.require(:default, :assets, Rails.env) 16 | end 17 | 18 | module SampleApp 19 | class Application < Rails::Application 20 | # Settings in config/environments/* take precedence over those specified here. 21 | # Application configuration should go into files in config/initializers 22 | # -- all .rb files in that directory are automatically loaded. 23 | 24 | # Custom directories with classes and modules you want to be autoloadable. 25 | # config.autoload_paths += %W(#{config.root}/extras) 26 | 27 | # Only load the plugins named here, in the order given (default is alphabetical). 28 | # :all can be used as a placeholder for all plugins not explicitly named. 29 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 30 | 31 | # Activate observers that should always be running. 32 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 33 | 34 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 35 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 36 | # config.time_zone = 'Central Time (US & Canada)' 37 | 38 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 39 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 40 | # config.i18n.default_locale = :de 41 | 42 | # Configure the default encoding used in templates for Ruby 1.9. 43 | config.encoding = "utf-8" 44 | 45 | # Configure sensitive parameters which will be filtered from the log file. 46 | config.filter_parameters += [:password] 47 | 48 | # Use SQL instead of Active Record's schema dumper when creating the database. 49 | # This is necessary if your schema can't be completely dumped by the schema dumper, 50 | # like if you have constraints or database-specific column types 51 | # config.active_record.schema_format = :sql 52 | 53 | # Enforce whitelist mode for mass assignment. 54 | # This will create an empty whitelist of attributes available for mass-assignment for all models 55 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 56 | # parameters by using an attr_accessible or attr_protected declaration. 57 | config.active_record.whitelist_attributes = true 58 | 59 | # Enable the asset pipeline 60 | config.assets.enabled = true 61 | 62 | # Version of your assets, change this if you want to expire all your assets 63 | config.assets.version = '1.0' 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" 3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" 4 | std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" 5 | %> 6 | default: <%= std_opts %> features 7 | wip: --tags @wip:3 --wip features 8 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip 9 | -------------------------------------------------------------------------------- /config/database.yml.example: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: &test 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | 27 | cucumber: 28 | <<: *test -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | SampleApp::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | SampleApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Raise exception on mass assignment protection for Active Record models 26 | config.active_record.mass_assignment_sanitizer = :strict 27 | 28 | # Log the query plan for queries taking more than this (works 29 | # with SQLite, MySQL, and PostgreSQL) 30 | config.active_record.auto_explain_threshold_in_seconds = 0.5 31 | 32 | # Do not compress assets 33 | config.assets.compress = false 34 | 35 | # Expands the lines which load the assets 36 | config.assets.debug = true 37 | end 38 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | SampleApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to Rails.root.join("public/assets") 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Prepend all log lines with the following tags 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 49 | # config.assets.precompile += %w( search.js ) 50 | 51 | # Disable delivery errors, bad email addresses will be ignored 52 | # config.action_mailer.raise_delivery_errors = false 53 | 54 | # Enable threaded mode 55 | # config.threadsafe! 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation can not be found) 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners 62 | config.active_support.deprecation = :notify 63 | 64 | # Log the query plan for queries taking more than this (works 65 | # with SQLite, MySQL, and PostgreSQL) 66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 67 | end 68 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | SampleApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Raise exception on mass assignment protection for Active Record models 33 | config.active_record.mass_assignment_sanitizer = :strict 34 | 35 | # Print deprecation notices to the stderr 36 | config.active_support.deprecation = :stderr 37 | 38 | require 'bcrypt' 39 | silence_warnings do 40 | BCrypt::Engine::DEFAULT_COST = BCrypt::Engine::MIN_COST 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | SampleApp::Application.config.secret_token = 'ced345e01611593c1b783bae98e4e56dbaee787747e92a141565f7c61d0ab2c6f98f7396fb4b178258301e2713816e158461af58c14b695901692f91e72b6200' 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | SampleApp::Application.config.session_store :cookie_store, key: '_sample_app_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # SampleApp::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | SampleApp::Application.routes.draw do 2 | resources :users do 3 | member do 4 | get :following, :followers 5 | end 6 | end 7 | resources :sessions, only: [:new, :create, :destroy] 8 | resources :microposts, only: [:create, :destroy] 9 | resources :relationships, only: [:create, :destroy] 10 | 11 | root to: 'static_pages#home' 12 | 13 | match '/signup', to: 'users#new' 14 | match '/signin', to: 'sessions#new' 15 | match '/signout', to: 'sessions#destroy', via: :delete 16 | 17 | match '/help', to: 'static_pages#help' 18 | match '/about', to: 'static_pages#about' 19 | match '/contact', to: 'static_pages#contact' 20 | 21 | # The priority is based upon order of creation: 22 | # first created -> highest priority. 23 | 24 | # Sample of regular route: 25 | # match 'products/:id' => 'catalog#view' 26 | # Keep in mind you can assign values other than :controller and :action 27 | 28 | # Sample of named route: 29 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 30 | # This route can be invoked with purchase_url(:id => product.id) 31 | 32 | # Sample resource route (maps HTTP verbs to controller actions automatically): 33 | # resources :products 34 | 35 | # Sample resource route with options: 36 | # resources :products do 37 | # member do 38 | # get 'short' 39 | # post 'toggle' 40 | # end 41 | # 42 | # collection do 43 | # get 'sold' 44 | # end 45 | # end 46 | 47 | # Sample resource route with sub-resources: 48 | # resources :products do 49 | # resources :comments, :sales 50 | # resource :seller 51 | # end 52 | 53 | # Sample resource route with more complex sub-resources 54 | # resources :products do 55 | # resources :comments 56 | # resources :sales do 57 | # get 'recent', :on => :collection 58 | # end 59 | # end 60 | 61 | # Sample resource route within a namespace: 62 | # namespace :admin do 63 | # # Directs /admin/products/* to Admin::ProductsController 64 | # # (app/controllers/admin/products_controller.rb) 65 | # resources :products 66 | # end 67 | 68 | # You can have the root of your site routed with "root" 69 | # just remember to delete public/index.html. 70 | # root :to => 'welcome#index' 71 | 72 | # See how all your routes lay out with "rake routes" 73 | 74 | # This is a legacy wild controller route that's not recommended for RESTful applications. 75 | # Note: This route will make all actions in every controller accessible via GET requests. 76 | # match ':controller(/:action(/:id))(.:format)' 77 | end 78 | -------------------------------------------------------------------------------- /db/migrate/20120308032820_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.string :email 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20120308034224_add_index_to_users_email.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToUsersEmail < ActiveRecord::Migration 2 | def change 3 | add_index :users, :email, unique: true 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/20120308034454_add_password_digest_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddPasswordDigestToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :password_digest, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120308054414_add_remember_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddRememberTokenToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :remember_token, :string 4 | add_index :users, :remember_token 5 | end 6 | end -------------------------------------------------------------------------------- /db/migrate/20120308193644_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120308210452_create_microposts.rb: -------------------------------------------------------------------------------- 1 | class CreateMicroposts < ActiveRecord::Migration 2 | def change 3 | create_table :microposts do |t| 4 | t.string :content 5 | t.integer :user_id 6 | 7 | t.timestamps 8 | end 9 | add_index :microposts, [:user_id, :created_at] 10 | end 11 | end -------------------------------------------------------------------------------- /db/migrate/20120308215846_create_relationships.rb: -------------------------------------------------------------------------------- 1 | class CreateRelationships < ActiveRecord::Migration 2 | def change 3 | create_table :relationships do |t| 4 | t.integer :follower_id 5 | t.integer :followed_id 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :relationships, :follower_id 11 | add_index :relationships, :followed_id 12 | add_index :relationships, [:follower_id, :followed_id], unique: true 13 | end 14 | end -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20120308215846) do 15 | 16 | create_table "microposts", :force => true do |t| 17 | t.string "content" 18 | t.integer "user_id" 19 | t.datetime "created_at", :null => false 20 | t.datetime "updated_at", :null => false 21 | end 22 | 23 | add_index "microposts", ["user_id", "created_at"], :name => "index_microposts_on_user_id_and_created_at" 24 | 25 | create_table "relationships", :force => true do |t| 26 | t.integer "follower_id" 27 | t.integer "followed_id" 28 | t.datetime "created_at", :null => false 29 | t.datetime "updated_at", :null => false 30 | end 31 | 32 | add_index "relationships", ["followed_id"], :name => "index_relationships_on_followed_id" 33 | add_index "relationships", ["follower_id", "followed_id"], :name => "index_relationships_on_follower_id_and_followed_id", :unique => true 34 | add_index "relationships", ["follower_id"], :name => "index_relationships_on_follower_id" 35 | 36 | create_table "users", :force => true do |t| 37 | t.string "name" 38 | t.string "email" 39 | t.datetime "created_at", :null => false 40 | t.datetime "updated_at", :null => false 41 | t.string "password_digest" 42 | t.string "remember_token" 43 | t.boolean "admin", :default => false 44 | end 45 | 46 | add_index "users", ["email"], :name => "index_users_on_email", :unique => true 47 | add_index "users", ["remember_token"], :name => "index_users_on_remember_token" 48 | 49 | end 50 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /features/signing_in.feature: -------------------------------------------------------------------------------- 1 | Feature: Signing in 2 | 3 | Scenario: Unsuccessful signin 4 | Given a user visits the signin page 5 | When they submit invalid signin information 6 | Then they should see an error message 7 | 8 | Scenario: Successful signin 9 | Given a user visits the signin page 10 | And the user has an account 11 | When the user submits valid signin information 12 | Then they should see their profile page 13 | And they should see a signout link -------------------------------------------------------------------------------- /features/step_definitions/authentication_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^a user visits the signin page$/ do 2 | visit signin_path 3 | end 4 | 5 | When /^they submit invalid signin information$/ do 6 | click_button "Sign in" 7 | end 8 | 9 | Then /^they should see an error message$/ do 10 | page.should have_selector('div.alert.alert-error') 11 | end 12 | 13 | Given /^the user has an account$/ do 14 | @user = User.create(name: "Example User", email: "user@example.com", 15 | password: "foobar", password_confirmation: "foobar") 16 | end 17 | 18 | When /^the user submits valid signin information$/ do 19 | visit signin_path 20 | fill_in "Email", with: @user.email 21 | fill_in "Password", with: @user.password 22 | click_button "Sign in" 23 | end 24 | 25 | Then /^they should see their profile page$/ do 26 | page.should have_selector('title', text: @user.name) 27 | end 28 | 29 | Then /^they should see a signout link$/ do 30 | page.should have_link('Sign out', href: signout_path) 31 | end -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | require 'cucumber/rails' 8 | 9 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In 10 | # order to ease the transition to Capybara we set the default here. If you'd 11 | # prefer to use XPath just remove this line and adjust any selectors in your 12 | # steps to use the XPath syntax. 13 | Capybara.default_selector = :css 14 | 15 | # By default, any exception happening in your Rails application will bubble up 16 | # to Cucumber so that your scenario will fail. This is a different from how 17 | # your application behaves in the production environment, where an error page will 18 | # be rendered instead. 19 | # 20 | # Sometimes we want to override this default behaviour and allow Rails to rescue 21 | # exceptions and display an error page (just like when the app is running in production). 22 | # Typical scenarios where you want to do this is when you test your error pages. 23 | # There are two ways to allow Rails to rescue exceptions: 24 | # 25 | # 1) Tag your scenario (or feature) with @allow-rescue 26 | # 27 | # 2) Set the value below to true. Beware that doing this globally is not 28 | # recommended as it will mask a lot of errors for you! 29 | # 30 | ActionController::Base.allow_rescue = false 31 | 32 | # Remove/comment out the lines below if your app doesn't have a database. 33 | # For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. 34 | begin 35 | DatabaseCleaner.strategy = :transaction 36 | rescue NameError 37 | raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." 38 | end 39 | 40 | # You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. 41 | # See the DatabaseCleaner documentation for details. Example: 42 | # 43 | # Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do 44 | # DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]} 45 | # end 46 | # 47 | # Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do 48 | # DatabaseCleaner.strategy = :transaction 49 | # end 50 | # 51 | 52 | # Possible values are :truncation and :transaction 53 | # The :transaction strategy is faster, but might give you threading problems. 54 | # See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature 55 | Cucumber::Rails::Database.javascript_strategy = :truncation 56 | 57 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 24 | t.binary = vendored_cucumber_bin 25 | t.fork = true # You may get faster startup if you set this to false 26 | t.profile = 'wip' 27 | end 28 | 29 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 30 | t.binary = vendored_cucumber_bin 31 | t.fork = true # You may get faster startup if you set this to false 32 | t.profile = 'rerun' 33 | end 34 | 35 | desc 'Run all features' 36 | task :all => [:ok, :wip] 37 | 38 | task :statsetup do 39 | require 'rails/code_statistics' 40 | ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') 41 | ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') 42 | end 43 | end 44 | desc 'Alias for cucumber:ok' 45 | task :cucumber => 'cucumber:ok' 46 | 47 | task :default => :cucumber 48 | 49 | task :features => :cucumber do 50 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 51 | end 52 | 53 | # In case we don't have ActiveRecord, append a no-op task that we can depend upon. 54 | task 'db:test:prepare' do 55 | end 56 | 57 | task :stats => 'cucumber:statsetup' 58 | rescue LoadError 59 | desc 'cucumber rake task not available (cucumber not installed)' 60 | task :cucumber do 61 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 62 | end 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /lib/tasks/sample_data.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc "Fill database with sample data" 3 | task populate: :environment do 4 | make_users 5 | make_microposts 6 | make_relationships 7 | end 8 | end 9 | 10 | def make_users 11 | admin = User.create!(name: "Example User", 12 | email: "example@railstutorial.org", 13 | password: "foobar", 14 | password_confirmation: "foobar") 15 | admin.toggle!(:admin) 16 | 99.times do |n| 17 | name = Faker::Name.name 18 | email = "example-#{n+1}@railstutorial.org" 19 | password = "password" 20 | User.create!(name: name, 21 | email: email, 22 | password: password, 23 | password_confirmation: password) 24 | end 25 | end 26 | 27 | def make_microposts 28 | users = User.all(limit: 6) 29 | 50.times do 30 | content = Faker::Lorem.sentence(5) 31 | users.each { |user| user.microposts.create!(content: content) } 32 | end 33 | end 34 | 35 | def make_relationships 36 | users = User.all 37 | user = users.first 38 | followed_users = users[2..50] 39 | followers = users[3..40] 40 | followed_users.each { |followed| user.follow!(followed) } 41 | followers.each { |follower| follower.follow!(user) } 42 | end -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/log/.gitkeep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The page you were looking for doesn't exist.

    23 |

    You may have mistyped the address or the page may have moved.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

    Maybe you tried to change something you didn't have access to.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    We're sorry, but something went wrong.

    23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 4 | if vendored_cucumber_bin 5 | load File.expand_path(vendored_cucumber_bin) 6 | else 7 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 8 | require 'cucumber' 9 | load Cucumber::BINARY 10 | end 11 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/controllers/relationships_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RelationshipsController do 4 | 5 | let(:user) { FactoryGirl.create(:user) } 6 | let(:other_user) { FactoryGirl.create(:user) } 7 | 8 | before { sign_in user } 9 | 10 | describe "creating a relationship with Ajax" do 11 | 12 | it "should increment the Relationship count" do 13 | expect do 14 | xhr :post, :create, relationship: { followed_id: other_user.id } 15 | end.to change(Relationship, :count).by(1) 16 | end 17 | 18 | it "should respond with success" do 19 | xhr :post, :create, relationship: { followed_id: other_user.id } 20 | response.should be_success 21 | end 22 | end 23 | 24 | describe "destroying a relationship with Ajax" do 25 | 26 | before { user.follow!(other_user) } 27 | let(:relationship) { user.relationships.find_by_followed_id(other_user) } 28 | 29 | it "should decrement the Relationship count" do 30 | expect do 31 | xhr :delete, :destroy, id: relationship.id 32 | end.to change(Relationship, :count).by(-1) 33 | end 34 | 35 | it "should respond with success" do 36 | xhr :delete, :destroy, id: relationship.id 37 | response.should be_success 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /spec/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | sequence(:name) { |n| "Person #{n}" } 4 | sequence(:email) { |n| "person_#{n}@example.com"} 5 | password "foobar" 6 | password_confirmation "foobar" 7 | 8 | factory :admin do 9 | admin true 10 | end 11 | end 12 | 13 | factory :micropost do 14 | content "Lorem ipsum" 15 | user 16 | end 17 | end -------------------------------------------------------------------------------- /spec/helpers/application_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ApplicationHelper do 4 | 5 | describe "full_title" do 6 | it "should include the page name" do 7 | full_title("foo").should =~ /foo/ 8 | end 9 | 10 | it "should includes the base name" do 11 | full_title("foo").should =~ /^Ruby on Rails Tutorial Sample App/ 12 | end 13 | 14 | it "should not include a bar for the home page" do 15 | full_title("").should_not =~ /\|/ 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/models/micropost_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Micropost do 4 | 5 | let(:user) { FactoryGirl.create(:user) } 6 | before { @micropost = user.microposts.build(content: "Lorem ipsum") } 7 | 8 | subject { @micropost } 9 | 10 | it { should respond_to(:content) } 11 | it { should respond_to(:user_id) } 12 | it { should respond_to(:user) } 13 | its(:user) { should == user } 14 | 15 | it { should be_valid } 16 | 17 | describe "accessible attributes" do 18 | it "should not allow access to user_id" do 19 | expect do 20 | Micropost.new(user_id: user.id) 21 | end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) 22 | end 23 | end 24 | 25 | describe "when user_id is not present" do 26 | before { @micropost.user_id = nil } 27 | it { should_not be_valid } 28 | end 29 | 30 | describe "with blank content" do 31 | before { @micropost.content = " " } 32 | it { should_not be_valid } 33 | end 34 | 35 | describe "with content that is too long" do 36 | before { @micropost.content = "a" * 141 } 37 | it { should_not be_valid } 38 | end 39 | end -------------------------------------------------------------------------------- /spec/models/relationship_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Relationship do 4 | 5 | let(:follower) { FactoryGirl.create(:user) } 6 | let(:followed) { FactoryGirl.create(:user) } 7 | let(:relationship) { follower.relationships.build(followed_id: followed.id) } 8 | 9 | subject { relationship } 10 | 11 | it { should be_valid } 12 | 13 | describe "accessible attributes" do 14 | it "should not allow access to follower_id" do 15 | expect do 16 | Relationship.new(follower_id: follower.id) 17 | end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) 18 | end 19 | end 20 | 21 | describe "follower methods" do 22 | it { should respond_to(:follower) } 23 | it { should respond_to(:followed) } 24 | its(:follower) { should == follower } 25 | its(:followed) { should == followed } 26 | end 27 | 28 | describe "when followed id is not present" do 29 | before { relationship.followed_id = nil } 30 | it { should_not be_valid } 31 | end 32 | 33 | describe "when follower id is not present" do 34 | before { relationship.follower_id = nil } 35 | it { should_not be_valid } 36 | end 37 | end -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe User do 4 | 5 | before do 6 | @user = User.new(name: "Example User", email: "user@example.com", 7 | password: "foobar", password_confirmation: "foobar") 8 | end 9 | 10 | subject { @user } 11 | 12 | it { should respond_to(:name) } 13 | it { should respond_to(:email) } 14 | it { should respond_to(:password_digest) } 15 | it { should respond_to(:password) } 16 | it { should respond_to(:password_confirmation) } 17 | it { should respond_to(:remember_token) } 18 | it { should respond_to(:admin) } 19 | it { should respond_to(:authenticate) } 20 | it { should respond_to(:microposts) } 21 | it { should respond_to(:feed) } 22 | it { should respond_to(:relationships) } 23 | it { should respond_to(:followed_users) } 24 | it { should respond_to(:reverse_relationships) } 25 | it { should respond_to(:followers) } 26 | it { should respond_to(:following?) } 27 | it { should respond_to(:follow!) } 28 | it { should respond_to(:unfollow!) } 29 | 30 | it { should be_valid } 31 | it { should_not be_admin } 32 | 33 | describe "accessible attributes" do 34 | it "should not allow access to admin" do 35 | expect do 36 | User.new(admin: true) 37 | end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) 38 | end 39 | end 40 | 41 | describe "with admin attribute set to 'true'" do 42 | before do 43 | @user.save! 44 | @user.toggle!(:admin) 45 | end 46 | 47 | it { should be_admin } 48 | end 49 | 50 | describe "when name is not present" do 51 | before { @user.name = " " } 52 | it { should_not be_valid } 53 | end 54 | 55 | describe "when email is not present" do 56 | before { @user.email = " " } 57 | it { should_not be_valid } 58 | end 59 | 60 | describe "when name is too long" do 61 | before { @user.name = "a" * 51 } 62 | it { should_not be_valid } 63 | end 64 | 65 | describe "when email format is invalid" do 66 | it "should be invalid" do 67 | addresses = %w[user@foo,com user_at_foo.org example.user@foo.] 68 | addresses.each do |invalid_address| 69 | @user.email = invalid_address 70 | @user.should_not be_valid 71 | end 72 | end 73 | end 74 | 75 | describe "when email format is valid" do 76 | it "should be valid" do 77 | addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn] 78 | addresses.each do |valid_address| 79 | @user.email = valid_address 80 | @user.should be_valid 81 | end 82 | end 83 | end 84 | 85 | describe "when email address is already taken" do 86 | before do 87 | user_with_same_email = @user.dup 88 | user_with_same_email.email = @user.email.upcase 89 | user_with_same_email.save 90 | end 91 | 92 | it { should_not be_valid } 93 | end 94 | 95 | describe "email address with mixed case" do 96 | let(:mixed_case_email) { "Foo@ExAMPle.CoM" } 97 | 98 | it "should be saved as all lower-case" do 99 | @user.email = mixed_case_email 100 | @user.save 101 | @user.reload.email.should == mixed_case_email.downcase 102 | end 103 | end 104 | 105 | describe "when password is not present" do 106 | before { @user.password = @user.password_confirmation = " " } 107 | it { should_not be_valid } 108 | end 109 | 110 | describe "when password doesn't match confirmation" do 111 | before { @user.password_confirmation = "mismatch" } 112 | it { should_not be_valid } 113 | end 114 | 115 | describe "when password confirmation is nil" do 116 | before { @user.password_confirmation = nil } 117 | it { should_not be_valid } 118 | end 119 | 120 | describe "with a password that's too short" do 121 | before { @user.password = @user.password_confirmation = "a" * 5 } 122 | it { should be_invalid } 123 | end 124 | 125 | describe "return value of authenticate method" do 126 | before { @user.save } 127 | let(:found_user) { User.find_by_email(@user.email) } 128 | 129 | describe "with valid password" do 130 | it { should == found_user.authenticate(@user.password) } 131 | end 132 | 133 | describe "with invalid password" do 134 | let(:user_for_invalid_password) { found_user.authenticate("invalid") } 135 | 136 | it { should_not == user_for_invalid_password } 137 | specify { user_for_invalid_password.should be_false } 138 | end 139 | end 140 | 141 | describe "remember token" do 142 | before { @user.save } 143 | its(:remember_token) { should_not be_blank } 144 | end 145 | 146 | 147 | describe "micropost associations" do 148 | 149 | before { @user.save } 150 | let!(:older_micropost) do 151 | FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago) 152 | end 153 | let!(:newer_micropost) do 154 | FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago) 155 | end 156 | 157 | it "should have the right microposts in the right order" do 158 | @user.microposts.should == [newer_micropost, older_micropost] 159 | end 160 | 161 | it "should destroy associated microposts" do 162 | microposts = @user.microposts.dup 163 | @user.destroy 164 | microposts.should_not be_empty 165 | microposts.each do |micropost| 166 | Micropost.find_by_id(micropost.id).should be_nil 167 | end 168 | end 169 | 170 | describe "status" do 171 | let(:unfollowed_post) do 172 | FactoryGirl.create(:micropost, user: FactoryGirl.create(:user)) 173 | end 174 | 175 | let(:followed_user) { FactoryGirl.create(:user) } 176 | 177 | before do 178 | @user.follow!(followed_user) 179 | 3.times { followed_user.microposts.create!(content: "Lorem ipsum") } 180 | end 181 | 182 | its(:feed) { should include(newer_micropost) } 183 | its(:feed) { should include(older_micropost) } 184 | its(:feed) { should_not include(unfollowed_post) } 185 | its(:feed) do 186 | followed_user.microposts.each do |micropost| 187 | should include(micropost) 188 | end 189 | end 190 | end 191 | end 192 | 193 | describe "following" do 194 | let(:other_user) { FactoryGirl.create(:user) } 195 | before do 196 | @user.save 197 | @user.follow!(other_user) 198 | end 199 | 200 | it { should be_following(other_user) } 201 | its(:followed_users) { should include(other_user) } 202 | 203 | describe "followed user" do 204 | subject { other_user } 205 | its(:followers) { should include(@user) } 206 | end 207 | 208 | describe "and unfollowing" do 209 | before { @user.unfollow!(other_user) } 210 | 211 | it { should_not be_following(other_user) } 212 | its(:followed_users) { should_not include(other_user) } 213 | end 214 | end 215 | end -------------------------------------------------------------------------------- /spec/requests/authentication_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Authentication" do 4 | 5 | subject { page } 6 | 7 | describe "signin page" do 8 | before { visit signin_path } 9 | 10 | it { should have_selector('h1', text: 'Sign in') } 11 | it { should have_selector('title', text: 'Sign in') } 12 | end 13 | 14 | describe "signin" do 15 | before { visit signin_path } 16 | 17 | describe "with invalid information" do 18 | before { click_button "Sign in" } 19 | 20 | it { should have_selector('title', text: 'Sign in') } 21 | it { should have_selector('div.alert.alert-error', text: 'Invalid') } 22 | 23 | describe "after visiting another page" do 24 | before { click_link "Home" } 25 | it { should_not have_selector('div.alert.alert-error') } 26 | end 27 | end 28 | 29 | describe "with valid information" do 30 | let(:user) { FactoryGirl.create(:user) } 31 | before do 32 | fill_in "Email", with: user.email.upcase 33 | fill_in "Password", with: user.password 34 | click_button "Sign in" 35 | end 36 | 37 | it { should have_selector('title', text: user.name) } 38 | 39 | it { should have_link('Users', href: users_path) } 40 | it { should have_link('Profile', href: user_path(user)) } 41 | it { should have_link('Settings', href: edit_user_path(user)) } 42 | it { should have_link('Sign out', href: signout_path) } 43 | it { should_not have_link('Sign in', href: signin_path) } 44 | 45 | describe "followed by signout" do 46 | before { click_link "Sign out" } 47 | it { should have_link('Sign in') } 48 | end 49 | end 50 | end 51 | 52 | describe "authorization" do 53 | 54 | describe "for non-signed-in users" do 55 | let(:user) { FactoryGirl.create(:user) } 56 | 57 | describe "when attempting to visit a protected page" do 58 | before do 59 | visit edit_user_path(user) 60 | fill_in "Email", with: user.email 61 | fill_in "Password", with: user.password 62 | click_button "Sign in" 63 | end 64 | 65 | describe "after signing in" do 66 | 67 | it "should render the desired protected page" do 68 | page.should have_selector('title', text: 'Edit user') 69 | end 70 | 71 | describe "when signing in again" do 72 | before do 73 | delete signout_path 74 | visit signin_path 75 | fill_in "Email", with: user.email 76 | fill_in "Password", with: user.password 77 | click_button "Sign in" 78 | end 79 | 80 | it "should render the default (profile) page" do 81 | page.should have_selector('title', text: user.name) 82 | end 83 | end 84 | end 85 | end 86 | 87 | describe "in the Users controller" do 88 | 89 | describe "visiting the edit page" do 90 | before { visit edit_user_path(user) } 91 | it { should have_selector('title', text: 'Sign in') } 92 | end 93 | 94 | describe "submitting to the update action" do 95 | before { put user_path(user) } 96 | specify { response.should redirect_to(signin_url) } 97 | end 98 | 99 | describe "visiting user index" do 100 | before { visit users_path } 101 | it { should have_selector('title', text: 'Sign in') } 102 | end 103 | 104 | describe "visiting the following page" do 105 | before { visit following_user_path(user) } 106 | it { should have_selector('title', text: 'Sign in') } 107 | end 108 | 109 | describe "visiting the followers page" do 110 | before { visit followers_user_path(user) } 111 | it { should have_selector('title', text: 'Sign in') } 112 | end 113 | end 114 | 115 | describe "in the Microposts controller" do 116 | 117 | describe "submitting to the create action" do 118 | before { post microposts_path } 119 | specify { response.should redirect_to(signin_url) } 120 | end 121 | 122 | describe "submitting to the destroy action" do 123 | before { delete micropost_path(FactoryGirl.create(:micropost)) } 124 | specify { response.should redirect_to(signin_url) } 125 | end 126 | end 127 | 128 | describe "in the Relationships controller" do 129 | describe "submitting to the create action" do 130 | before { post relationships_path } 131 | specify { response.should redirect_to(signin_url) } 132 | end 133 | 134 | describe "submitting to the destroy action" do 135 | before { delete relationship_path(1) } 136 | specify { response.should redirect_to(signin_url) } 137 | end 138 | end 139 | end 140 | 141 | describe "as wrong user" do 142 | let(:user) { FactoryGirl.create(:user) } 143 | let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") } 144 | before { sign_in user } 145 | 146 | describe "visiting Users#edit page" do 147 | before { visit edit_user_path(wrong_user) } 148 | it { should have_selector('title', text: full_title('')) } 149 | end 150 | 151 | describe "submitting a PUT request to the Users#update action" do 152 | before { put user_path(wrong_user) } 153 | specify { response.should redirect_to(root_url) } 154 | end 155 | end 156 | 157 | describe "as non-admin user" do 158 | let(:user) { FactoryGirl.create(:user) } 159 | let(:non_admin) { FactoryGirl.create(:user) } 160 | 161 | before { sign_in non_admin } 162 | 163 | describe "submitting a DELETE request to the Users#destroy action" do 164 | before { delete user_path(user) } 165 | specify { response.should redirect_to(root_url) } 166 | end 167 | end 168 | end 169 | end -------------------------------------------------------------------------------- /spec/requests/micropost_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Micropost pages" do 4 | 5 | subject { page } 6 | 7 | let(:user) { FactoryGirl.create(:user) } 8 | before { sign_in user } 9 | 10 | describe "micropost creation" do 11 | before { visit root_path } 12 | 13 | describe "with invalid information" do 14 | 15 | it "should not create a micropost" do 16 | expect { click_button "Post" }.not_to change(Micropost, :count) 17 | end 18 | 19 | describe "error messages" do 20 | before { click_button "Post" } 21 | it { should have_content('error') } 22 | end 23 | end 24 | 25 | describe "with valid information" do 26 | 27 | before { fill_in 'micropost_content', with: "Lorem ipsum" } 28 | it "should create a micropost" do 29 | expect { click_button "Post" }.to change(Micropost, :count).by(1) 30 | end 31 | end 32 | end 33 | 34 | describe "micropost destruction" do 35 | before { FactoryGirl.create(:micropost, user: user) } 36 | 37 | describe "as correct user" do 38 | before { visit root_path } 39 | 40 | it "should delete a micropost" do 41 | expect { click_link "delete" }.to change(Micropost, :count).by(-1) 42 | end 43 | end 44 | end 45 | end -------------------------------------------------------------------------------- /spec/requests/static_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Static pages" do 4 | 5 | subject { page } 6 | 7 | describe "Home page" do 8 | before { visit root_path } 9 | 10 | it { should have_selector('h1', text: 'Sample App') } 11 | it { should have_selector('title', text: full_title('')) } 12 | it { should_not have_selector 'title', text: '| Home' } 13 | 14 | describe "for signed-in users" do 15 | let(:user) { FactoryGirl.create(:user) } 16 | before do 17 | FactoryGirl.create(:micropost, user: user, content: "Lorem ipsum") 18 | FactoryGirl.create(:micropost, user: user, content: "Dolor sit amet") 19 | sign_in user 20 | visit root_path 21 | end 22 | 23 | it "should render the user's feed" do 24 | user.feed.each do |item| 25 | page.should have_selector("li##{item.id}", text: item.content) 26 | end 27 | end 28 | 29 | describe "follower/following counts" do 30 | let(:other_user) { FactoryGirl.create(:user) } 31 | before do 32 | other_user.follow!(user) 33 | visit root_path 34 | end 35 | 36 | it { should have_link("0 following", href: following_user_path(user)) } 37 | it { should have_link("1 followers", href: followers_user_path(user)) } 38 | end 39 | end 40 | end 41 | 42 | describe "Help page" do 43 | before { visit help_path } 44 | 45 | it { should have_selector('h1', text: 'Help') } 46 | it { should have_selector('title', text: full_title('Help')) } 47 | end 48 | 49 | describe "About page" do 50 | before { visit about_path } 51 | 52 | it { should have_selector('h1', text: 'About') } 53 | it { should have_selector('title', text: full_title('About Us')) } 54 | end 55 | 56 | describe "Contact page" do 57 | before { visit contact_path } 58 | 59 | it { should have_selector('h1', text: 'Contact') } 60 | it { should have_selector('title', text: full_title('Contact')) } 61 | end 62 | 63 | it "should have the right links on the layout" do 64 | visit root_path 65 | click_link "About" 66 | page.should have_selector 'title', text: full_title('About Us') 67 | click_link "Help" 68 | page.should have_selector 'title', text: full_title('Help') 69 | click_link "Contact" 70 | page.should have_selector 'title', text: full_title('Contact') 71 | click_link "Home" 72 | click_link "Sign up now!" 73 | page.should have_selector 'title', text: full_title('Sign up') 74 | click_link "sample app" 75 | page.should have_selector 'h1', text: 'Sample App' 76 | end 77 | end -------------------------------------------------------------------------------- /spec/requests/user_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "User pages" do 4 | 5 | subject { page } 6 | 7 | describe "index" do 8 | 9 | before do 10 | sign_in FactoryGirl.create(:user) 11 | FactoryGirl.create(:user, name: "Bob", email: "bob@example.com") 12 | FactoryGirl.create(:user, name: "Ben", email: "ben@example.com") 13 | visit users_path 14 | end 15 | 16 | it { should have_selector('title', text: 'All users') } 17 | 18 | describe "pagination" do 19 | 20 | before(:all) { 30.times { FactoryGirl.create(:user) } } 21 | after(:all) { User.delete_all } 22 | 23 | let(:first_page) { User.paginate(page: 1) } 24 | let(:second_page) { User.paginate(page: 2) } 25 | 26 | it { should have_link('Next') } 27 | its(:html) { should match('>2') } 28 | 29 | it "should list each user" do 30 | User.all[0..2].each do |user| 31 | page.should have_selector('li', text: user.name) 32 | end 33 | end 34 | 35 | it "should list the first page of users" do 36 | first_page.each do |user| 37 | page.should have_selector('li', text: user.name) 38 | end 39 | end 40 | 41 | it "should not list the second page of users" do 42 | second_page.each do |user| 43 | page.should_not have_selector('li', text: user.name) 44 | end 45 | end 46 | 47 | describe "showing the second page" do 48 | before { visit users_path(page: 2) } 49 | 50 | it "should list the second page of users" do 51 | second_page.each do |user| 52 | page.should have_selector('li', text: user.name) 53 | end 54 | end 55 | end 56 | end 57 | 58 | describe "delete links" do 59 | 60 | it { should_not have_link('delete') } 61 | 62 | describe "as an admin user" do 63 | let(:admin) { FactoryGirl.create(:admin) } 64 | before do 65 | sign_in admin 66 | visit users_path 67 | end 68 | 69 | it { should have_link('delete', href: user_path(User.first)) } 70 | it "should be able to delete another user" do 71 | expect { click_link('delete') }.to change(User, :count).by(-1) 72 | end 73 | it { should_not have_link('delete', href: user_path(admin)) } 74 | end 75 | end 76 | end 77 | 78 | describe "profile page" do 79 | let(:user) { FactoryGirl.create(:user) } 80 | let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") } 81 | let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") } 82 | 83 | before { visit user_path(user) } 84 | 85 | it { should have_selector('h1', text: user.name) } 86 | it { should have_selector('title', text: user.name) } 87 | 88 | describe "microposts" do 89 | it { should have_content(m1.content) } 90 | it { should have_content(m2.content) } 91 | it { should have_content(user.microposts.count) } 92 | end 93 | 94 | describe "follow/unfollow buttons" do 95 | let(:other_user) { FactoryGirl.create(:user) } 96 | before { sign_in user } 97 | 98 | describe "following a user" do 99 | before { visit user_path(other_user) } 100 | 101 | it "should increment the followed user count" do 102 | expect do 103 | click_button "Follow" 104 | end.to change(user.followed_users, :count).by(1) 105 | end 106 | 107 | it "should increment the other user's followers count" do 108 | expect do 109 | click_button "Follow" 110 | end.to change(other_user.followers, :count).by(1) 111 | end 112 | 113 | describe "toggling the button" do 114 | before { click_button "Follow" } 115 | it { should have_selector('input', value: 'Unfollow') } 116 | end 117 | end 118 | 119 | describe "unfollowing a user" do 120 | before do 121 | user.follow!(other_user) 122 | visit user_path(other_user) 123 | end 124 | 125 | it "should decrement the followed user count" do 126 | expect do 127 | click_button "Unfollow" 128 | end.to change(user.followed_users, :count).by(-1) 129 | end 130 | 131 | it "should decrement the other user's followers count" do 132 | expect do 133 | click_button "Unfollow" 134 | end.to change(other_user.followers, :count).by(-1) 135 | end 136 | 137 | describe "toggling the button" do 138 | before { click_button "Unfollow" } 139 | it { should have_selector('input', value: 'Follow') } 140 | end 141 | end 142 | end 143 | end 144 | 145 | describe "signup page" do 146 | before { visit signup_path } 147 | 148 | it { should have_selector('h1', text: 'Sign up') } 149 | it { should have_selector('title', text: full_title('Sign up')) } 150 | end 151 | 152 | describe "signup" do 153 | 154 | before { visit signup_path } 155 | 156 | let(:submit) { "Create my account" } 157 | 158 | describe "with invalid information" do 159 | it "should not create a user" do 160 | expect { click_button submit }.not_to change(User, :count) 161 | end 162 | 163 | describe "error messages" do 164 | before { click_button submit } 165 | 166 | it { should have_selector('title', text: 'Sign up') } 167 | it { should have_content('error') } 168 | end 169 | end 170 | 171 | describe "with valid information" do 172 | before do 173 | fill_in "Name", with: "Example User" 174 | fill_in "Email", with: "user@example.com" 175 | fill_in "Password", with: "foobar" 176 | fill_in "Confirmation", with: "foobar" 177 | end 178 | 179 | it "should create a user" do 180 | expect { click_button submit }.to change(User, :count).by(1) 181 | end 182 | 183 | describe "after saving the user" do 184 | before { click_button submit } 185 | 186 | let(:user) { User.find_by_email('user@example.com') } 187 | 188 | it { should have_selector('title', text: user.name) } 189 | it { should have_selector('div.alert.alert-success', text: 'Welcome') } 190 | it { should have_link('Sign out') } 191 | end 192 | end 193 | end 194 | 195 | describe "edit" do 196 | let(:user) { FactoryGirl.create(:user) } 197 | before do 198 | sign_in user 199 | visit edit_user_path(user) 200 | end 201 | 202 | describe "page" do 203 | it { should have_selector('h1', text: "Update your profile") } 204 | it { should have_selector('title', text: "Edit user") } 205 | it { should have_link('change', href: 'http://gravatar.com/emails') } 206 | end 207 | 208 | describe "with invalid information" do 209 | before { click_button "Save changes" } 210 | 211 | it { should have_content('error') } 212 | end 213 | 214 | describe "with valid information" do 215 | let(:new_name) { "New Name" } 216 | let(:new_email) { "new@example.com" } 217 | before do 218 | fill_in "Name", with: new_name 219 | fill_in "Email", with: new_email 220 | fill_in "Password", with: user.password 221 | fill_in "Confirm Password", with: user.password 222 | click_button "Save changes" 223 | end 224 | 225 | it { should have_selector('title', text: new_name) } 226 | it { should have_selector('div.alert.alert-success') } 227 | it { should have_link('Sign out', href: signout_path) } 228 | specify { user.reload.name.should == new_name } 229 | specify { user.reload.email.should == new_email } 230 | end 231 | end 232 | 233 | describe "following/followers" do 234 | let(:user) { FactoryGirl.create(:user) } 235 | let(:other_user) { FactoryGirl.create(:user) } 236 | before { user.follow!(other_user) } 237 | 238 | describe "followed users" do 239 | before do 240 | sign_in user 241 | visit following_user_path(user) 242 | end 243 | 244 | it { should have_selector('title', text: full_title('Following')) } 245 | it { should have_selector('h3', text: 'Following') } 246 | it { should have_link(other_user.name, href: user_path(other_user)) } 247 | end 248 | 249 | describe "followers" do 250 | before do 251 | sign_in other_user 252 | visit followers_user_path(other_user) 253 | end 254 | 255 | it { should have_selector('title', text: full_title('Followers')) } 256 | it { should have_selector('h3', text: 'Followers') } 257 | it { should have_link(user.name, href: user_path(user)) } 258 | end 259 | end 260 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spork' 3 | #uncomment the following line to use spork with the debugger 4 | #require 'spork/ext/ruby-debug' 5 | 6 | Spork.prefork do 7 | # Loading more in this block will cause your tests to run faster. However, 8 | # if you change any configuration or code from libraries loaded here, you'll 9 | # need to restart spork for it take effect. 10 | # This file is copied to spec/ when you run 'rails generate rspec:install' 11 | ENV["RAILS_ENV"] ||= 'test' 12 | require File.expand_path("../../config/environment", __FILE__) 13 | require 'rspec/rails' 14 | require 'rspec/autorun' 15 | 16 | # Requires supporting ruby files with custom matchers and macros, etc, 17 | # in spec/support/ and its subdirectories. 18 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 19 | 20 | RSpec.configure do |config| 21 | # ## Mock Framework 22 | # 23 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 24 | # 25 | # config.mock_with :mocha 26 | # config.mock_with :flexmock 27 | # config.mock_with :rr 28 | config.mock_with :rspec 29 | 30 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 31 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 32 | 33 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 34 | # examples within a transaction, remove the following line or assign false 35 | # instead of true. 36 | config.use_transactional_fixtures = true 37 | 38 | # If true, the base class of anonymous controllers will be inferred 39 | # automatically. This will be the default behavior in future versions of 40 | # rspec-rails. 41 | config.infer_base_class_for_anonymous_controllers = false 42 | end 43 | end 44 | 45 | Spork.each_run do 46 | # This code will be run each time you run your specs. 47 | 48 | end 49 | -------------------------------------------------------------------------------- /spec/support/utilities.rb: -------------------------------------------------------------------------------- 1 | include ApplicationHelper 2 | 3 | def sign_in(user) 4 | visit signin_path 5 | fill_in "Email", with: user.email 6 | fill_in "Password", with: user.password 7 | click_button "Sign in" 8 | # Sign in when not using Capybara as well. 9 | cookies[:remember_token] = user.remember_token 10 | end -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/vendor/assets/javascripts/.gitkeep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/vendor/assets/stylesheets/.gitkeep -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railstutorial/sample_app_2nd_ed/e59c4fc43eb1763ed00ed9ff27f57a97d488434c/vendor/plugins/.gitkeep --------------------------------------------------------------------------------