├── .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 │ ├── concerns │ │ ├── .keep │ │ └── signed_in_user.rb │ ├── microposts_controller.rb │ ├── relationships_controller.rb │ ├── sessions_controller.rb │ ├── static_pages_controller.rb │ ├── users │ │ ├── followers_controller.rb │ │ └── following_controller.rb │ └── users_controller.rb ├── decorators │ └── micropost_decorator.rb ├── helpers │ ├── application_helper.rb │ ├── sessions_helper.rb │ ├── static_pages_helper.rb │ └── users_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ ├── .keep │ │ └── emailable.rb │ ├── micropost.rb │ ├── relationship.rb │ └── user.rb ├── presenters │ └── users │ │ ├── follow_presenter.rb │ │ ├── followed_users_presenter.rb │ │ └── followers_presenter.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 │ ├── _contact_justin.html.erb │ ├── about.html.erb │ ├── contact.html.erb │ ├── help.html.erb │ ├── home.html.erb │ └── show.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 ├── bin ├── bundle ├── git-railsconf.zsh ├── rails └── rake ├── 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 │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20130311191400_create_users.rb │ ├── 20130311194153_add_index_to_users_email.rb │ ├── 20130311201841_add_password_digest_to_users.rb │ ├── 20130314184954_add_remember_token_to_users.rb │ ├── 20130315015932_add_admin_to_users.rb │ ├── 20130315175534_create_microposts.rb │ ├── 20130315230445_create_relationships.rb │ └── 20140405044857_add_profanity_counter_to_user.rb ├── schema.rb └── seeds.rb ├── features ├── signing_in.feature ├── step_definitions │ └── authentication_steps.rb └── support │ └── env.rb ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ ├── cucumber.rake │ └── sample_data.rake ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── assets │ ├── application-4962059d8f80f9bb096692bacc29c4e8.css │ ├── application-4962059d8f80f9bb096692bacc29c4e8.css.gz │ ├── application-eeb856e3fe2c8f879c91d0e81d59cb40.js │ ├── application-eeb856e3fe2c8f879c91d0e81d59cb40.js.gz │ ├── glyphicons-halflings-c806376f05e4ccabe2c5315a8e95667c.png │ ├── glyphicons-halflings-white-62b67d9edee3db90d18833087f848d6e.png │ ├── manifest-802de9eb1c853769101852422b620883.json │ └── rails-231a680f23887d9dd70710ea5efd3c62.png ├── favicon.ico └── robots.txt ├── script └── cucumber ├── spec ├── controllers │ ├── microposts_controller_spec.rb │ └── relationships_controller_spec.rb ├── decorators │ └── micropost_decorator_spec.rb ├── factories.rb ├── helpers │ └── application_helper_spec.rb ├── models │ ├── concerns │ │ └── emailable_spec.rb │ ├── 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 └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore the default SQLite database. 5 | /db/*.sqlite3 6 | /db/*.sqlite3-journal 7 | 8 | # Ignore all logfiles and tempfiles. 9 | /log/*.log 10 | /tmp 11 | 12 | # Ignore other unneeded files. 13 | database.yml 14 | doc/ 15 | *.swp 16 | *~ 17 | .project 18 | .DS_Store 19 | .idea 20 | .rvmrc 21 | .ruby-* 22 | .secret -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --drb 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.0.0' 3 | #ruby-gemset=railstutorial_rails_4_0 4 | 5 | gem 'rails', '4.0.4' 6 | gem 'draper' 7 | gem 'bootstrap-sass', '2.3.2.0' 8 | gem 'sprockets', '2.11.0' 9 | gem 'bcrypt-ruby', '3.1.2' 10 | gem 'faker', '1.1.2' 11 | gem 'will_paginate', '3.0.4' 12 | gem 'bootstrap-will_paginate', '0.0.9' 13 | gem 'awesome_print' 14 | 15 | group :development do 16 | gem 'pry-rails' 17 | end 18 | 19 | group :development, :test do 20 | gem 'sqlite3', '1.3.8' 21 | gem 'rspec-rails', '2.14' 22 | # The following optional lines are part of the advanced setup. 23 | gem 'guard-rails' 24 | gem 'guard-rspec' 25 | gem 'rspec-mocks' 26 | gem 'spork-rails', '4.0.0' 27 | gem 'guard-spork', '1.5.0' 28 | gem 'childprocess', '0.3.6' 29 | end 30 | 31 | group :test do 32 | gem 'selenium-webdriver', '2.35.1' 33 | gem 'capybara', '2.1.0' 34 | gem 'factory_girl_rails', '4.2.0' 35 | gem 'cucumber-rails', '1.3.0', :require => false 36 | gem 'database_cleaner', github: 'bmabey/database_cleaner' 37 | 38 | # Uncomment this line on OS X. 39 | gem 'growl', '1.0.3' 40 | 41 | # Uncomment these lines on Linux. 42 | # gem 'libnotify', '0.8.0' 43 | 44 | # Uncomment these lines on Windows. 45 | # gem 'rb-notifu', '0.0.4' 46 | # gem 'win32console', '1.3.2' 47 | # gem 'wdm', '0.1.0' 48 | end 49 | 50 | gem 'sass-rails', '4.0.1' 51 | gem 'uglifier', '2.1.1' 52 | gem 'coffee-rails', '4.0.1' 53 | gem 'jquery-rails', '3.0.4' 54 | gem 'turbolinks', '1.1.1' 55 | gem 'jbuilder', '1.0.2' 56 | 57 | group :doc do 58 | gem 'sdoc', '0.3.20', require: false 59 | end 60 | 61 | group :production do 62 | gem 'pg', '0.15.1' 63 | gem 'rails_12factor', '0.0.2' 64 | end 65 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/bmabey/database_cleaner.git 3 | revision: 35bd646903fc56af185a9fa9e360c568bb56756e 4 | specs: 5 | database_cleaner (1.2.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionmailer (4.0.4) 11 | actionpack (= 4.0.4) 12 | mail (~> 2.5.4) 13 | actionpack (4.0.4) 14 | activesupport (= 4.0.4) 15 | builder (~> 3.1.0) 16 | erubis (~> 2.7.0) 17 | rack (~> 1.5.2) 18 | rack-test (~> 0.6.2) 19 | activemodel (4.0.4) 20 | activesupport (= 4.0.4) 21 | builder (~> 3.1.0) 22 | activerecord (4.0.4) 23 | activemodel (= 4.0.4) 24 | activerecord-deprecated_finders (~> 1.0.2) 25 | activesupport (= 4.0.4) 26 | arel (~> 4.0.0) 27 | activerecord-deprecated_finders (1.0.3) 28 | activesupport (4.0.4) 29 | i18n (~> 0.6, >= 0.6.9) 30 | minitest (~> 4.2) 31 | multi_json (~> 1.3) 32 | thread_safe (~> 0.1) 33 | tzinfo (~> 0.3.37) 34 | arel (4.0.2) 35 | awesome_print (1.2.0) 36 | bcrypt-ruby (3.1.2) 37 | bootstrap-sass (2.3.2.0) 38 | sass (~> 3.2) 39 | bootstrap-will_paginate (0.0.9) 40 | will_paginate 41 | builder (3.1.4) 42 | capybara (2.1.0) 43 | mime-types (>= 1.16) 44 | nokogiri (>= 1.3.3) 45 | rack (>= 1.0.0) 46 | rack-test (>= 0.5.4) 47 | xpath (~> 2.0) 48 | celluloid (0.15.2) 49 | timers (~> 1.1.0) 50 | celluloid-io (0.15.0) 51 | celluloid (>= 0.15.0) 52 | nio4r (>= 0.5.0) 53 | childprocess (0.3.6) 54 | ffi (~> 1.0, >= 1.0.6) 55 | coderay (1.1.0) 56 | coffee-rails (4.0.1) 57 | coffee-script (>= 2.2.0) 58 | railties (>= 4.0.0, < 5.0) 59 | coffee-script (2.2.0) 60 | coffee-script-source 61 | execjs 62 | coffee-script-source (1.7.0) 63 | cucumber (1.3.12) 64 | builder (>= 2.1.2) 65 | diff-lcs (>= 1.1.3) 66 | gherkin (~> 2.12) 67 | multi_json (>= 1.7.5, < 2.0) 68 | multi_test (>= 0.1.1) 69 | cucumber-rails (1.3.0) 70 | capybara (>= 1.1.2) 71 | cucumber (>= 1.1.8) 72 | nokogiri (>= 1.5.0) 73 | diff-lcs (1.2.5) 74 | draper (1.3.0) 75 | actionpack (>= 3.0) 76 | activemodel (>= 3.0) 77 | activesupport (>= 3.0) 78 | request_store (~> 1.0.3) 79 | erubis (2.7.0) 80 | execjs (2.0.2) 81 | factory_girl (4.2.0) 82 | activesupport (>= 3.0.0) 83 | factory_girl_rails (4.2.0) 84 | factory_girl (~> 4.2.0) 85 | railties (>= 3.0.0) 86 | faker (1.1.2) 87 | i18n (~> 0.5) 88 | ffi (1.9.3) 89 | formatador (0.2.4) 90 | gherkin (2.12.2) 91 | multi_json (~> 1.3) 92 | growl (1.0.3) 93 | guard (2.6.0) 94 | formatador (>= 0.2.4) 95 | listen (~> 2.7) 96 | lumberjack (~> 1.0) 97 | pry (>= 0.9.12) 98 | thor (>= 0.18.1) 99 | guard-rails (0.5.0) 100 | guard (>= 2.0.0) 101 | guard-rspec (4.2.8) 102 | guard (~> 2.1) 103 | rspec (>= 2.14, < 4.0) 104 | guard-spork (1.5.0) 105 | childprocess (>= 0.2.3) 106 | guard (>= 1.1) 107 | spork (>= 0.8.4) 108 | hike (1.2.3) 109 | i18n (0.6.9) 110 | jbuilder (1.0.2) 111 | activesupport (>= 3.0.0) 112 | jquery-rails (3.0.4) 113 | railties (>= 3.0, < 5.0) 114 | thor (>= 0.14, < 2.0) 115 | json (1.8.1) 116 | listen (2.7.1) 117 | celluloid (>= 0.15.2) 118 | celluloid-io (>= 0.15.0) 119 | rb-fsevent (>= 0.9.3) 120 | rb-inotify (>= 0.9) 121 | lumberjack (1.0.5) 122 | mail (2.5.4) 123 | mime-types (~> 1.16) 124 | treetop (~> 1.4.8) 125 | method_source (0.8.2) 126 | mime-types (1.25.1) 127 | mini_portile (0.5.2) 128 | minitest (4.7.5) 129 | multi_json (1.9.2) 130 | multi_test (0.1.1) 131 | nio4r (1.0.0) 132 | nokogiri (1.6.1) 133 | mini_portile (~> 0.5.0) 134 | pg (0.15.1) 135 | polyglot (0.3.4) 136 | pry (0.9.12.6) 137 | coderay (~> 1.0) 138 | method_source (~> 0.8) 139 | slop (~> 3.4) 140 | pry-rails (0.3.2) 141 | pry (>= 0.9.10) 142 | rack (1.5.2) 143 | rack-test (0.6.2) 144 | rack (>= 1.0) 145 | rails (4.0.4) 146 | actionmailer (= 4.0.4) 147 | actionpack (= 4.0.4) 148 | activerecord (= 4.0.4) 149 | activesupport (= 4.0.4) 150 | bundler (>= 1.3.0, < 2.0) 151 | railties (= 4.0.4) 152 | sprockets-rails (~> 2.0.0) 153 | rails_12factor (0.0.2) 154 | rails_serve_static_assets 155 | rails_stdout_logging 156 | rails_serve_static_assets (0.0.2) 157 | rails_stdout_logging (0.0.3) 158 | railties (4.0.4) 159 | actionpack (= 4.0.4) 160 | activesupport (= 4.0.4) 161 | rake (>= 0.8.7) 162 | thor (>= 0.18.1, < 2.0) 163 | rake (10.3.1) 164 | rb-fsevent (0.9.4) 165 | rb-inotify (0.9.3) 166 | ffi (>= 0.5.0) 167 | rdoc (3.12.2) 168 | json (~> 1.4) 169 | request_store (1.0.5) 170 | rspec (2.14.1) 171 | rspec-core (~> 2.14.0) 172 | rspec-expectations (~> 2.14.0) 173 | rspec-mocks (~> 2.14.0) 174 | rspec-core (2.14.8) 175 | rspec-expectations (2.14.5) 176 | diff-lcs (>= 1.1.3, < 2.0) 177 | rspec-mocks (2.14.6) 178 | rspec-rails (2.14.0) 179 | actionpack (>= 3.0) 180 | activesupport (>= 3.0) 181 | railties (>= 3.0) 182 | rspec-core (~> 2.14.0) 183 | rspec-expectations (~> 2.14.0) 184 | rspec-mocks (~> 2.14.0) 185 | rubyzip (0.9.9) 186 | sass (3.3.3) 187 | sass-rails (4.0.1) 188 | railties (>= 4.0.0, < 5.0) 189 | sass (>= 3.1.10) 190 | sprockets-rails (~> 2.0.0) 191 | sdoc (0.3.20) 192 | json (>= 1.1.3) 193 | rdoc (~> 3.10) 194 | selenium-webdriver (2.35.1) 195 | childprocess (>= 0.2.5) 196 | multi_json (~> 1.0) 197 | rubyzip (< 1.0.0) 198 | websocket (~> 1.0.4) 199 | slop (3.5.0) 200 | spork (1.0.0rc4) 201 | spork-rails (4.0.0) 202 | rails (>= 3.0.0, < 5) 203 | spork (>= 1.0rc0) 204 | sprockets (2.11.0) 205 | hike (~> 1.2) 206 | multi_json (~> 1.0) 207 | rack (~> 1.0) 208 | tilt (~> 1.1, != 1.3.0) 209 | sprockets-rails (2.0.1) 210 | actionpack (>= 3.0) 211 | activesupport (>= 3.0) 212 | sprockets (~> 2.8) 213 | sqlite3 (1.3.8) 214 | thor (0.19.1) 215 | thread_safe (0.3.3) 216 | tilt (1.4.1) 217 | timers (1.1.0) 218 | treetop (1.4.15) 219 | polyglot 220 | polyglot (>= 0.3.1) 221 | turbolinks (1.1.1) 222 | coffee-rails 223 | tzinfo (0.3.39) 224 | uglifier (2.1.1) 225 | execjs (>= 0.3.0) 226 | multi_json (~> 1.0, >= 1.0.2) 227 | websocket (1.0.7) 228 | will_paginate (3.0.4) 229 | xpath (2.0.0) 230 | nokogiri (~> 1.3) 231 | 232 | PLATFORMS 233 | ruby 234 | 235 | DEPENDENCIES 236 | awesome_print 237 | bcrypt-ruby (= 3.1.2) 238 | bootstrap-sass (= 2.3.2.0) 239 | bootstrap-will_paginate (= 0.0.9) 240 | capybara (= 2.1.0) 241 | childprocess (= 0.3.6) 242 | coffee-rails (= 4.0.1) 243 | cucumber-rails (= 1.3.0) 244 | database_cleaner! 245 | draper 246 | factory_girl_rails (= 4.2.0) 247 | faker (= 1.1.2) 248 | growl (= 1.0.3) 249 | guard-rails 250 | guard-rspec 251 | guard-spork (= 1.5.0) 252 | jbuilder (= 1.0.2) 253 | jquery-rails (= 3.0.4) 254 | pg (= 0.15.1) 255 | pry-rails 256 | rails (= 4.0.4) 257 | rails_12factor (= 0.0.2) 258 | rspec-mocks 259 | rspec-rails (= 2.14) 260 | sass-rails (= 4.0.1) 261 | sdoc (= 0.3.20) 262 | selenium-webdriver (= 2.35.1) 263 | spork-rails (= 4.0.0) 264 | sprockets (= 2.11.0) 265 | sqlite3 (= 1.3.8) 266 | turbolinks (= 1.1.1) 267 | uglifier (= 2.1.1) 268 | will_paginate (= 3.0.4) 269 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | require 'active_support/inflector' 4 | 5 | guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, 6 | :rspec_env => { 'RAILS_ENV' => 'test' } do 7 | watch('config/application.rb') 8 | watch('config/environment.rb') 9 | watch('config/environments/test.rb') 10 | watch(%r{^config/initializers/.+\.rb$}) 11 | watch('Gemfile') 12 | watch('Gemfile.lock') 13 | watch('spec/spec_helper.rb') { :rspec } 14 | watch('test/test_helper.rb') { :test_unit } 15 | watch(%r{features/support/}) { :cucumber } 16 | end 17 | 18 | guard 'rspec', 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{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 25 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 26 | 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"] } 27 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 28 | watch('config/routes.rb') { "spec/routing" } 29 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 30 | 31 | # Capybara features specs 32 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" } 33 | 34 | # Turnip features and steps 35 | watch(%r{^spec/acceptance/(.+)\.feature$}) 36 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 37 | 38 | # Custom Rails Tutorial specs 39 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m| 40 | ["spec/routing/#{m[1]}_routing_spec.rb", 41 | "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", 42 | "spec/acceptance/#{m[1]}_spec.rb", 43 | (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" : 44 | "spec/requests/#{m[1].singularize}_pages_spec.rb")] 45 | end 46 | watch(%r{^app/views/(.+)/}) do |m| 47 | (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" : 48 | "spec/requests/#{m[1].singularize}_pages_spec.rb") 49 | end 50 | watch(%r{^app/controllers/sessions_controller\.rb$}) do |m| 51 | "spec/requests/authentication_pages_spec.rb" 52 | end 53 | end 54 | 55 | guard 'rails' do 56 | watch('Gemfile.lock') 57 | watch(%r{^(config|lib)/.*}) 58 | end 59 | 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Michael Hartl, 2014 Justin Gordon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Refactoring Fat Models, Controllers, and Views Example 2 | 3 | [RailsOnMaui Links to slides, talk, etc.](http://www.railsonmaui.com/blog/2014/04/23/railsconf-2014/) 4 | 5 | This example application covers four Rails Refactoring techniques: 6 | Concerns, Decorators, Presenters, and moving code to models. 7 | It builds on the Ruby on Rails Tutorial: sample application by Michael Hartl: 8 | [*Ruby on Rails Tutorial: Learn Web Development with Rails*](http://railstutorial.org/). 9 | However, it is changed in that minors cannot post profanity. If you login as 10 | "littlepunk@sugarranchmaui.com", password "foobar", you get the minor profanity 11 | checking behavior. There is no UI for setting a user to be a minor, FYI. 12 | 13 | The code is carefully crafted so that the initial code (at branch `railsconf-start` 14 | is relatively "clean". The lessons can thus focus only on the refactoring techniques, 15 | which are presented several pull requests. Much of the lesson information is contained 16 | in the pull request description. 17 | 18 | Please inspect the individual commits, as the refactorings are broken up into smaller 19 | staps. The final result, the tip of the branch, contains the full refactoring. 20 | Please feel free to comment on the pull requests and/or submit issues. You may also 21 | contact me directly at [justin@shakacode.com](mailto:justin@shakacode.com). 22 | 23 | The branches labeled `rc-` are the final versions developed for my RailsConf 2014 presentation, 24 | titled "Concerns, Decorators, Presenters, Service Objects, Helpers, Help Me Decide!". 25 | 26 | Topic | Branch | Pull Request 27 | -----------|--------|------ 28 | Concerns | rc-concerns | https://github.com/justin808/fat-code-refactoring-techniques/pull/9 29 | Decorators | rc-decorators | https://github.com/justin808/fat-code-refactoring-techniques/pull/10 30 | Presenters | rc-presenters | https://github.com/justin808/fat-code-refactoring-techniques/pull/11 31 | Models rather than Service Objects | rc-business-logic-in-model | https://github.com/justin808/fat-code-refactoring-techniques/pull/15 32 | Split Controllers | rc-split-controller | https://github.com/justin808/fat-code-refactoring-techniques/pull/13 33 | Controller Concerns | rc-controller-concerns | https://github.com/justin808/fat-code-refactoring-techniques/pull/14 34 | 35 | Note, I originally planned to cover "Service Objects". However, my example of a refactoring the 36 | "Kid Safe Microblogger" actually demonstrated how a Service Object pattern is not needed. Instead, 37 | the lesson is to convert to using core Rails techniques of moving logic to business models. You 38 | can find the prior two refactoring attempts here: 39 | 40 | * [Pull 6, Service Objects](https://github.com/justin808/fat-code-refactoring-techniques/pull/6). 41 | * [Pull 7, Focused Controller](https://github.com/justin808/fat-code-refactoring-techniques/pull/7). 42 | 43 | # Setup 44 | 45 | The command `guard` both runs the tests and the application. 46 | To re-run all tests, in the `guard` console window, type `a `. 47 | 48 | This will create a branch called `refactoring-tutorial` where you can follow the examples. 49 | 50 | ```bash 51 | $ cd /tmp 52 | $ git clone https://github.com/justin808/fat-code-refactoring-techniques.git 53 | $ git checkout railsconf-start 54 | $ git checkout -b refactoring-tutorial 55 | $ cd fat-code-refactoring-techniques 56 | $ cp config/database.yml.example config/database.yml 57 | $ bundle install 58 | $ bundle exec rake db:migrate 59 | $ bundle exec rake db:test:prepare 60 | $ guard 61 | ``` 62 | 63 | Then, if you want to see the completed application: 64 | 65 | ```bash 66 | $ git checkout railsconf-finish 67 | $ bundle install 68 | $ guard 69 | ``` 70 | 71 | I would suggest creating your `refactoring-tutorial` branch and then manually applying the commits 72 | in the pull requests, creating your own commits along the way. 73 | 74 | At each commit, the tests should continue to pass. 75 | 76 | You can simulate the flow of what I'll be doing in the presentation with the git scripts in `bin/git-railsconf.zsh`. 77 | 78 | ```bash 79 | $ cd 80 | $ . bin/git-railsconf.zsh 81 | $ git checkout railsconf-start # you're at the beginning of the refactorings 82 | ``` 83 | 84 | Then you can run this to simulate the first edit: 85 | 86 | ```bash 87 | $ railsconf-start 88 | ``` 89 | 90 | Take a look at the files that are changed. See the tests pass. Experiment. Then run: 91 | 92 | ```bash 93 | $ railsconf-advance-history 94 | ``` 95 | 96 | That will move the history forward one step, and the changing files will be in the git index. 97 | 98 | *WARNING*: These scripts mercilessly do `git reset --hard`. So beware! 99 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | SampleApp::Application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/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 | // 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 turbolinks 16 | //= require bootstrap 17 | //= require_tree . 18 | -------------------------------------------------------------------------------- /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://coffeescript.org/ 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://coffeescript.org/ 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://coffeescript.org/ 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.2em; 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 | margin-bottom: 15px; 185 | @include box_sizing; 186 | } 187 | 188 | input { 189 | height: auto !important; 190 | } 191 | 192 | #error_explanation { 193 | color: #f00; 194 | ul { 195 | list-style: none; 196 | margin: 0 0 18px 0; 197 | } 198 | } 199 | 200 | .field_with_errors { 201 | @extend .control-group; 202 | @extend .error; 203 | } 204 | 205 | /* Users index */ 206 | 207 | .users { 208 | list-style: none; 209 | margin: 0; 210 | li { 211 | overflow: auto; 212 | padding: 10px 0; 213 | border-top: 1px solid $grayLighter; 214 | &:last-child { 215 | border-bottom: 1px solid $grayLighter; 216 | } 217 | } 218 | } 219 | 220 | /* microposts */ 221 | 222 | .microposts { 223 | list-style: none; 224 | margin: 10px 0 0 0; 225 | 226 | li { 227 | padding: 10px 0; 228 | border-top: 1px solid #e8e8e8; 229 | } 230 | } 231 | .content { 232 | display: block; 233 | } 234 | .timestamp { 235 | color: $grayLight; 236 | } 237 | .gravatar { 238 | float: left; 239 | margin-right: 10px; 240 | } 241 | aside { 242 | textarea { 243 | height: 100px; 244 | margin-bottom: 5px; 245 | } 246 | } 247 | 248 | .parent-notification { 249 | font-size: 32px; 250 | font-weight: bold; 251 | } 252 | -------------------------------------------------------------------------------- /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 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | include SessionsHelper 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/concerns/signed_in_user.rb: -------------------------------------------------------------------------------- 1 | # Controller Concern Example 2 | # Place this in controllers that have all actions having a signed in user 3 | # include SignedInUser 4 | module SignedInUser 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | before_action :signed_in_user 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/microposts_controller.rb: -------------------------------------------------------------------------------- 1 | class MicropostsController < ApplicationController 2 | before_action :signed_in_user, only: [:create, :destroy] 3 | before_action :correct_user, only: :destroy 4 | 5 | def create 6 | @micropost = Micropost.new(micropost_params.merge(user: current_user)) 7 | if @micropost.save_with_profanity_callbacks 8 | flash[:success] = "Micropost created!" 9 | redirect_to root_url 10 | else 11 | adjust_micropost_profanity_message 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 | def micropost_params 23 | params.require(:micropost).permit(:content) 24 | end 25 | 26 | def correct_user 27 | @micropost = current_user.microposts.find_by(id: params[:id]) 28 | redirect_to root_url if @micropost.nil? 29 | end 30 | 31 | def adjust_micropost_profanity_message 32 | if @micropost.profanity_validation_error? 33 | @micropost.errors[:content].clear # remove the default validation message 34 | flash.now[:error] = @micropost.decorate.profanity_violation_msg 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/relationships_controller.rb: -------------------------------------------------------------------------------- 1 | class RelationshipsController < ApplicationController 2 | before_action :signed_in_user 3 | 4 | def create 5 | @user = User.find(params[:relationship][:followed_id]) 6 | current_user.follow!(@user) 7 | respond_to do |format| 8 | format.html { redirect_to @user } 9 | format.js 10 | end 11 | end 12 | 13 | def destroy 14 | @user = Relationship.find(params[:id]).followed 15 | current_user.unfollow!(@user) 16 | respond_to do |format| 17 | format.html { redirect_to @user } 18 | format.js 19 | end 20 | end 21 | 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/followers_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class FollowersController < ApplicationController 3 | include SignedInUser 4 | 5 | def followers 6 | @presenter = Users::FollowersPresenter.new(params[:id], params[:page]) 7 | render '/users/show_follow' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/users/following_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class FollowingController < ApplicationController 3 | include SignedInUser 4 | 5 | def following 6 | @presenter = Users::FollowedUsersPresenter.new(params[:id], params[:page]) 7 | render '/users/show_follow' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :signed_in_user, 3 | only: [:index, :edit, :update, :destroy, :following, :followers] 4 | before_action :correct_user, only: [:edit, :update] 5 | before_action :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(user_params) 22 | if @user.save 23 | sign_in @user 24 | flash[:success] = "Welcome to the Kid Blogger!" 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(user_params) 36 | flash[:success] = "Profile updated" 37 | redirect_to @user 38 | else 39 | render 'edit' 40 | end 41 | end 42 | 43 | def destroy 44 | User.find(params[:id]).destroy 45 | flash[:success] = "User destroyed." 46 | redirect_to users_url 47 | end 48 | 49 | private 50 | 51 | def user_params 52 | params.require(:user).permit(:name, :email, :password, 53 | :password_confirmation) 54 | end 55 | 56 | # Before filters 57 | 58 | def correct_user 59 | @user = User.find(params[:id]) 60 | redirect_to(root_url) unless current_user?(@user) 61 | end 62 | 63 | def admin_user 64 | redirect_to(root_url) unless current_user.admin? 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/decorators/micropost_decorator.rb: -------------------------------------------------------------------------------- 1 | class MicropostDecorator < Draper::Decorator 2 | 3 | def posted_ago 4 | h.content_tag :span, class: 'timestamp' do 5 | "Posted #{h.time_ago_in_words(object.created_at)} ago." 6 | end 7 | end 8 | 9 | def profanity_violation_msg 10 | <<-MSG.html_safe 11 |

Whoa, better watch your language! Profanity: '#{object.profane_words_in_content.join(", ")}' not allowed! 12 | You've tried to use profanity #{h.pluralize(object.user.profanity_count, "time")}! 13 |

Your parents have been notified!

14 | MSG 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /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 = "Kid Blogger" 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 | remember_token = User.new_remember_token 5 | cookies.permanent[:remember_token] = remember_token 6 | user.update_attribute(:remember_token, User.hash(remember_token)) 7 | self.current_user = user 8 | end 9 | 10 | def signed_in? 11 | !current_user.nil? 12 | end 13 | 14 | def current_user=(user) 15 | @current_user = user 16 | end 17 | 18 | def current_user 19 | remember_token = User.hash(cookies[:remember_token]) 20 | @current_user ||= User.find_by(remember_token: remember_token) 21 | end 22 | 23 | def current_user?(user) 24 | user == current_user 25 | end 26 | 27 | def signed_in_user 28 | unless signed_in? 29 | store_location 30 | redirect_to signin_url, notice: "Please sign in." 31 | end 32 | end 33 | 34 | def sign_out 35 | current_user.update_attribute(:remember_token, 36 | User.hash(User.new_remember_token)) 37 | cookies.delete(:remember_token) 38 | self.current_user = nil 39 | end 40 | 41 | def redirect_back_or(default) 42 | redirect_to(session[:return_to] || default) 43 | session.delete(:return_to) 44 | end 45 | 46 | def store_location 47 | session[:return_to] = request.url if request.get? 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /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_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" 8 | image_tag(gravatar_url, alt: user.name, class: "gravatar") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/emailable.rb: -------------------------------------------------------------------------------- 1 | module Emailable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_save { self.email = email.downcase } 6 | 7 | VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i 8 | validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, 9 | uniqueness: { case_sensitive: false } 10 | 11 | # downcase the searched for email 12 | scope :by_email_wildcard, ->(q) { where("email like ?", "#{q.downcase}%") } 13 | end 14 | 15 | def email_domain 16 | regex = /\A[\w+\-.]+@([a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z)/i 17 | email[regex, 1] 18 | end 19 | 20 | module ClassMethods 21 | def by_email(email) 22 | where(email: email.downcase).first 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/micropost.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: microposts 4 | # 5 | # id :integer not null, primary key 6 | # content :string(255) 7 | # user_id :integer 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | class Micropost < ActiveRecord::Base 13 | belongs_to :user 14 | default_scope -> { order('created_at DESC') } 15 | validates :content, presence: true, length: { maximum: 140 } 16 | validates :user_id, presence: true 17 | validate :no_profanity 18 | 19 | # Returns microposts from the users being followed by the given user. 20 | def self.from_users_followed_by(user) 21 | followed_user_ids = "SELECT followed_id FROM relationships 22 | WHERE follower_id = :user_id" 23 | where("user_id IN (#{followed_user_ids}) OR user_id = :user_id", 24 | user_id: user.id) 25 | end 26 | 27 | # return array of profane words in content or nil if none 28 | def profane_words_in_content 29 | # Better to set this somewhere configurable. Placing here for example purposes. 30 | profane_words = %w(poop fart fartface poopface poopbuttface) 31 | content_words = content.split(/\W/) 32 | content_words.select { |word| word.in? profane_words }.presence 33 | end 34 | 35 | # This could have been done with and after_save hook, but it seems wrong to ever be sending 36 | # emails from after_save. 37 | # Return true if save successful 38 | def save_with_profanity_callbacks 39 | transaction do 40 | valid = save 41 | if profanity_validation_error? 42 | user.update_for_using_profanity(profane_words_in_content) 43 | end 44 | valid 45 | end 46 | end 47 | 48 | def profanity_validation_error? 49 | errors[:content].find { |error| error =~ /\AProfanity:/ } 50 | end 51 | 52 | private 53 | 54 | def no_profanity 55 | if user && user.minor? && (profane_words = profane_words_in_content) 56 | errors.add(:content, "Profanity: #{profane_words.join(", ")} not allowed!") 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/models/relationship.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: relationships 4 | # 5 | # id :integer not null, primary key 6 | # follower_id :integer 7 | # followed_id :integer 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | class Relationship < ActiveRecord::Base 13 | belongs_to :follower, class_name: "User", touch: true 14 | belongs_to :followed, class_name: "User", touch: true 15 | validates :follower_id, presence: true 16 | validates :followed_id, presence: true 17 | end 18 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # email :string(255) 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # password_digest :string(255) 11 | # remember_token :string(255) 12 | # admin :boolean 13 | # profanity_count :integer default(0) 14 | # minor :boolean default(FALSE) 15 | # 16 | 17 | class User < ActiveRecord::Base 18 | include Emailable 19 | has_many :microposts, dependent: :destroy 20 | has_many :relationships, foreign_key: "follower_id", dependent: :destroy 21 | has_many :followed_users, through: :relationships, source: :followed 22 | has_many :reverse_relationships, foreign_key: "followed_id", 23 | class_name: "Relationship", 24 | dependent: :destroy 25 | has_many :followers, through: :reverse_relationships, source: :follower 26 | 27 | before_create :create_remember_token 28 | validates :name, presence: true, length: { maximum: 50 } 29 | 30 | has_secure_password 31 | validates :password, length: { minimum: 6 } 32 | 33 | def User.new_remember_token 34 | SecureRandom.urlsafe_base64 35 | end 36 | 37 | def User.hash(token) 38 | Digest::SHA1.hexdigest(token.to_s) 39 | end 40 | 41 | def feed 42 | Micropost.from_users_followed_by(self) 43 | end 44 | 45 | def following?(other_user) 46 | relationships.find_by(followed_id: other_user.id) 47 | end 48 | 49 | def follow!(other_user) 50 | relationships.create!(followed_id: other_user.id) 51 | end 52 | 53 | def unfollow!(other_user) 54 | relationships.find_by(followed_id: other_user.id).destroy 55 | end 56 | 57 | def update_for_using_profanity(profane_words_used) 58 | increment(:profanity_count, profane_words_used.size) 59 | save(validate: false) 60 | send_parent_notification_of_profanity(profane_words_used) 61 | end 62 | 63 | def send_parent_notification_of_profanity(profane_words) 64 | # PRETEND: send email 65 | Rails.logger.info("Sent profanity alert email to parent of #{name}, "\ 66 | "who used profane words: #{profane_words}") 67 | end 68 | 69 | private 70 | 71 | def create_remember_token 72 | self.remember_token = User.hash(User.new_remember_token) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /app/presenters/users/follow_presenter.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class FollowPresenter 3 | include Draper::ViewHelpers 4 | 5 | def initialize(user_id, page) 6 | @user_id = user_id 7 | @page = page 8 | end 9 | 10 | def user 11 | @user ||= User.find(@user_id) 12 | end 13 | 14 | def cache_key 15 | [user, title] 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/presenters/users/followed_users_presenter.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class FollowedUsersPresenter < FollowPresenter 3 | def users 4 | @users ||= user.followed_users.paginate(page: @page) 5 | end 6 | 7 | def title 8 | "Following" 9 | end 10 | 11 | def subtitle 12 | @subtitle ||= "You Are Following #{h.pluralize(user.followed_users.size, "Blogger")}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/presenters/users/followers_presenter.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class FollowersPresenter < FollowPresenter 3 | def users 4 | @users ||= user.followers.paginate(page: @page) 5 | end 6 | 7 | def title 8 | "Followers" 9 | end 10 | 11 | def subtitle 12 | @subtitle ||= "Your Got #{h.pluralize(user.followers.size, "Followers")}" 13 | end 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | Rails On Maui Blog 4 | by Justin Gordon 5 | 6 | 12 |
13 | -------------------------------------------------------------------------------- /app/views/layouts/_header.html.erb: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /app/views/layouts/_shim.html.erb: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= full_title(yield(:title)) %> 5 | <%= stylesheet_link_tag "application", media: "all", 6 | "data-turbolinks-track" => true %> 7 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %> 8 | <%= csrf_meta_tags %> 9 | <%= render 'layouts/shim' %> 10 | 11 | 12 | <%= render 'layouts/header' %> 13 |
14 | <% flash.each do |key, value| %> 15 |
<%= value %>
16 | <% end %> 17 | <%= yield %> 18 | <%= render 'layouts/footer' %> 19 | <%= debug(params) if Rails.env.development? %> 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /app/views/microposts/_micropost.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= micropost.content %> 3 | <%= micropost.decorate.posted_ago %> 4 | <% if current_user?(micropost.user) %> 5 | <%= link_to "delete", micropost, method: :delete, 6 | data: { confirm: "You sure?" }, 7 | title: micropost.content %> 8 | <% end %> 9 |
  • 10 | -------------------------------------------------------------------------------- /app/views/relationships/create.js.erb: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>") 2 | $("#followers").html('<%= @user.followers.count %>') 3 | -------------------------------------------------------------------------------- /app/views/relationships/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("<%= escape_javascript(render('users/follow')) %>") 2 | $("#followers").html('<%= @user.followers.count %>') 3 | -------------------------------------------------------------------------------- /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 |
    20 | -------------------------------------------------------------------------------- /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 |
      7 | <% object.errors.full_messages.each do |msg| %> 8 |
    • * <%= msg %>
    • 9 | <% end %> 10 |
    11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/shared/_feed.html.erb: -------------------------------------------------------------------------------- 1 | <% if @feed_items.try(:any?) %> 2 |
      3 | <%= render partial: 'shared/feed_item', collection: @feed_items %> 4 |
    5 | <%= will_paginate @feed_items %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /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 | <%= feed_item.decorate.posted_ago %> 8 | <% if current_user?(feed_item.user) %> 9 | <%= link_to "delete", feed_item, method: :delete, 10 | data: { confirm: "You sure?" }, 11 | title: feed_item.content %> 12 | <% end %> 13 |
  • 14 | -------------------------------------------------------------------------------- /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 %> 8 | -------------------------------------------------------------------------------- /app/views/shared/_stats.html.erb: -------------------------------------------------------------------------------- 1 | <% @user ||= current_user %> 2 | 16 | -------------------------------------------------------------------------------- /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 | 13 | -------------------------------------------------------------------------------- /app/views/static_pages/_contact_justin.html.erb: -------------------------------------------------------------------------------- 1 |

    2 | You may email Justin Gordon about the refactorings in this tutorial. 3 | Read more about Justin at his blog Rails on Maui. 4 |

    5 | -------------------------------------------------------------------------------- /app/views/static_pages/about.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'About Us') %> 2 |

    About Us

    3 |

    4 | This example application teaches 4 Rails Refactoring techniques: 5 | Concerns, Decorators, Presenters, and Service Objects. 6 |

    7 | <%= render 'contact_justin' %> 8 |

    9 | This example builds on the Ruby on Rails Tutorial 10 | by Michael Hartl. 11 |

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

    Contact

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

    Help

    3 |

    4 | Please comment about this tutorial in either the 5 | fat-code-refactoring-techniques Github Issues 6 | or in the pull requests. 7 |

    8 | <%= render 'contact_justin' %> 9 |

    10 | Additional References: 11 |

    19 |

    20 | -------------------------------------------------------------------------------- /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 Kid Safe Microblogging App

    22 | 23 |

    24 | This is the home page for the 25 | Rails on Maui Fat Code Refactoring 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 %> 35 | -------------------------------------------------------------------------------- /app/views/static_pages/show.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/app/views/static_pages/show.html.erb -------------------------------------------------------------------------------- /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 %> 10 | -------------------------------------------------------------------------------- /app/views/users/_unfollow.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(current_user.relationships.find_by(followed_id: @user.id), 2 | html: { method: :delete }, 3 | remote: true) do |f| %> 4 | <%= f.submit "Unfollow", class: "btn btn-large" %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /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 |
  • 9 | -------------------------------------------------------------------------------- /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 |
    28 | -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, 'All users') %> 2 |

    All users

    3 | 4 | <%= will_paginate %> 5 | 6 | 9 | 10 | <%= will_paginate %> 11 | -------------------------------------------------------------------------------- /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 | 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, "Confirmation" %> 19 | <%= f.password_field :password_confirmation %> 20 | 21 | <%= f.submit "Create my account", class: "btn btn-large btn-primary" %> 22 | <% end %> 23 |
    24 |
    25 | -------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, @user.name) %> 2 |
    3 | 24 |
    25 | <%= render 'follow_form' if signed_in? %> 26 | <% if @user.microposts.any? %> 27 |

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

    28 |
      29 | <%= render @microposts %> 30 |
    31 | <%= will_paginate @microposts %> 32 | <% end %> 33 |
    34 |
    35 | -------------------------------------------------------------------------------- /app/views/users/show_follow.html.erb: -------------------------------------------------------------------------------- 1 | <% provide(:title, @presenter.title) %> 2 | 3 | <% cache @presenter.cache_key do %> 4 |
    5 | 23 |
    24 |

    <%= @presenter.subtitle %>

    25 | <% if @presenter.users.any? %> 26 |
      27 | <%= render collection: @presenter.users, partial: 'users/user' %> 28 |
    29 | <%= will_paginate @presenter.users %> 30 | <% end %> 31 |
    32 |
    33 | <% end %> 34 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/git-railsconf.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # cd to top level of this git repo, then run 4 | 5 | # . bin/git-railsconf.zsh 6 | 7 | # BE SURE TO SET RAILSCONF_DEMO= 8 | export RAILSCONF_DEMO=`pwd` 9 | 10 | git-child-sha() { 11 | branch=${1:-master} 12 | git log --ancestry-path --format=%H ${commit}..$branch | tail -1 13 | } 14 | 15 | git-advance-history() { 16 | branch=${1:-master} 17 | sha=$(git-child-sha $branch) 18 | git --no-pager show --pretty --quiet $sha 19 | git checkout $sha 20 | } 21 | 22 | git-advance-history-reset-soft() { 23 | branch=${1:-master} 24 | git reset --hard HEAD 25 | git-advance-history $branch 26 | git-advance-history $branch 27 | git reset --soft HEAD\^ 28 | } 29 | 30 | 31 | 32 | # START HERE 33 | railsconf-start() { 34 | cd $RAILSCONF_DEMO 35 | git checkout railsconf-start 36 | git-advance-history railsconf-finish 37 | git reset --soft HEAD\^ 38 | } 39 | 40 | 41 | # ADVANCE BY USING THIS 42 | # Assumes starting point 43 | # 1. is NOT a branch 44 | # 2. has files checked out 45 | # NOTE: goes a git reset --hard, so you lose any changes! 46 | railsconf-advance-history() { 47 | git-advance-history-reset-soft railsconf-finish 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /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 "sprockets/railtie" 8 | # require "rails/test_unit/railtie" 9 | 10 | # Assets should be precompiled for production (so we don't need the gems loaded then) 11 | Bundler.require(*Rails.groups(assets: %w(development test))) 12 | 13 | module SampleApp 14 | class Application < Rails::Application 15 | # Settings in config/environments/* take precedence over those specified here. 16 | # Application configuration should go into files in config/initializers 17 | # -- all .rb files in that directory are automatically loaded. 18 | 19 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 20 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 21 | # config.time_zone = 'Central Time (US & Canada)' 22 | 23 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 24 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 25 | # config.i18n.default_locale = :de 26 | # I18n.enforce_available_locales = true 27 | I18n.enforce_available_locales = true 28 | 29 | config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) 30 | 31 | config.autoload_paths += %W( 32 | #{config.root}/app/presenters 33 | ) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /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 | # Do not eager load code on boot. 10 | config.eager_load = false 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 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | config.assets.debug = true 27 | end 28 | -------------------------------------------------------------------------------- /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 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | config.serve_static_assets = false 23 | 24 | # Compress JavaScripts and CSS. 25 | config.assets.js_compressor = :uglifier 26 | # config.assets.css_compressor = :sass 27 | 28 | # Whether to fallback to assets pipeline if a precompiled asset is missed. 29 | config.assets.compile = false 30 | 31 | # Generate digests for assets URLs. 32 | config.assets.digest = true 33 | 34 | # Version of your assets, change this if you want to expire all your assets. 35 | config.assets.version = '1.0' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | config.force_ssl = false 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :debug 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | # Precompile additional assets. 60 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 61 | # config.assets.precompile += %w( search.js ) 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation can not be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Disable automatic flushing of the log to improve performance. 75 | # config.autoflush_log = false 76 | 77 | # Use default logging formatter so that PID and timestamp are not suppressed. 78 | config.log_formatter = ::Logger::Formatter.new 79 | end 80 | -------------------------------------------------------------------------------- /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 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Speed up tests by lowering bcrypt's cost function. 38 | ActiveModel::SecurePassword.min_cost = true 39 | end 40 | -------------------------------------------------------------------------------- /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/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /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. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /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 | # Make sure your secret_key_base is kept private 4 | # if you're sharing your code publicly, such as by adding 5 | # .secret to your .gitignore file. 6 | 7 | require 'securerandom' 8 | 9 | def secure_token 10 | token_file = Rails.root.join('.secret') 11 | if File.exist?(token_file) 12 | # Use the existing token. 13 | File.read(token_file).chomp 14 | else 15 | # Generate a new token and store it in token_file. 16 | token = SecureRandom.hex(64) 17 | File.write(token_file, token) 18 | token 19 | end 20 | end 21 | 22 | SampleApp::Application.config.secret_key_base = secure_token -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | SampleApp::Application.routes.draw do 2 | resources :users do 3 | scope module: 'users' do 4 | member do 5 | get :followers, controller: 'followers' 6 | get :following, controller: 'following' 7 | end 8 | end 9 | end 10 | 11 | resources :sessions, only: [:new, :create, :destroy] 12 | resources :microposts, only: [:create, :destroy] 13 | resources :relationships, only: [:create, :destroy] 14 | root to: 'static_pages#home' 15 | match '/signup', to: 'users#new', via: 'get' 16 | match '/signin', to: 'sessions#new', via: 'get' 17 | match '/signout', to: 'sessions#destroy', via: 'delete' 18 | match '/help', to: 'static_pages#help', via: 'get' 19 | match '/about', to: 'static_pages#about', via: 'get' 20 | match '/contact', to: 'static_pages#contact', via: 'get' 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20130311191400_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/20130311194153_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 6 | -------------------------------------------------------------------------------- /db/migrate/20130311201841_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/20130314184954_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 7 | -------------------------------------------------------------------------------- /db/migrate/20130315015932_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :admin, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130315175534_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 12 | -------------------------------------------------------------------------------- /db/migrate/20130315230445_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 | add_index :relationships, :follower_id 10 | add_index :relationships, :followed_id 11 | add_index :relationships, [:follower_id, :followed_id], unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20140405044857_add_profanity_counter_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddProfanityCounterToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :profanity_count, :integer, default: 0 4 | add_column :users, :minor, :boolean, default: false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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 that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20140405044857) do 15 | 16 | create_table "microposts", force: true do |t| 17 | t.string "content" 18 | t.integer "user_id" 19 | t.datetime "created_at" 20 | t.datetime "updated_at" 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" 29 | t.datetime "updated_at" 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" 40 | t.datetime "updated_at" 41 | t.string "password_digest" 42 | t.string "remember_token" 43 | t.boolean "admin" 44 | t.integer "profanity_count", default: 0 45 | t.boolean "minor", default: false 46 | end 47 | 48 | add_index "users", ["email"], name: "index_users_on_email", unique: true 49 | add_index "users", ["remember_token"], name: "index_users_on_remember_token" 50 | 51 | end 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | expect(page).to 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 | fill_in "Email", with: @user.email 20 | fill_in "Password", with: @user.password 21 | click_button "Sign in" 22 | end 23 | 24 | Then /^they should see their profile page$/ do 25 | expect(page).to have_title(@user.name) 26 | end 27 | 28 | Then /^they should see a signout link$/ do 29 | expect(page).to have_link('Sign out', href: signout_path) 30 | 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 | # # { :except => [:widgets] } may not do what you expect here 45 | # # as tCucumber::Rails::Database.javascript_strategy overrides 46 | # # this setting. 47 | # DatabaseCleaner.strategy = :truncation 48 | # end 49 | # 50 | # Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do 51 | # DatabaseCleaner.strategy = :transaction 52 | # end 53 | # 54 | 55 | # Possible values are :truncation and :transaction 56 | # The :transaction strategy is faster, but might give you threading problems. 57 | # See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature 58 | Cucumber::Rails::Database.javascript_strategy = :truncation 59 | 60 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/lib/tasks/.keep -------------------------------------------------------------------------------- /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: true) 16 | minor = User.create!(name: "Little Punk", 17 | email: "littlepunk@sugarranchmaui.com", 18 | password: "foobar", 19 | password_confirmation: "foobar", 20 | admin: false, 21 | minor: true) 22 | 99.times do |n| 23 | name = Faker::Name.name 24 | email = "example-#{n+1}@railstutorial.org" 25 | password = "password" 26 | User.create!(name: name, 27 | email: email, 28 | password: password, 29 | password_confirmation: password) 30 | end 31 | end 32 | 33 | def make_microposts 34 | users = User.all(limit: 6) 35 | 50.times do 36 | content = Faker::Lorem.sentence(5) 37 | users.each { |user| user.microposts.create!(content: content) } 38 | end 39 | end 40 | 41 | def make_relationships 42 | users = User.all 43 | user = users.first 44 | followed_users = users[2..50] 45 | followers = users[3..40] 46 | followed_users.each { |followed| user.follow!(followed) } 47 | followers.each { |follower| follower.follow!(user) } 48 | end 49 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/log/.keep -------------------------------------------------------------------------------- /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 |

    If you are the application owner check the logs for more information.

    26 | 27 | 28 | -------------------------------------------------------------------------------- /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 |

    If you are the application owner check the logs for more information.

    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/assets/application-4962059d8f80f9bb096692bacc29c4e8.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/public/assets/application-4962059d8f80f9bb096692bacc29c4e8.css.gz -------------------------------------------------------------------------------- /public/assets/application-eeb856e3fe2c8f879c91d0e81d59cb40.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/public/assets/application-eeb856e3fe2c8f879c91d0e81d59cb40.js.gz -------------------------------------------------------------------------------- /public/assets/glyphicons-halflings-c806376f05e4ccabe2c5315a8e95667c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/public/assets/glyphicons-halflings-c806376f05e4ccabe2c5315a8e95667c.png -------------------------------------------------------------------------------- /public/assets/glyphicons-halflings-white-62b67d9edee3db90d18833087f848d6e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/public/assets/glyphicons-halflings-white-62b67d9edee3db90d18833087f848d6e.png -------------------------------------------------------------------------------- /public/assets/manifest-802de9eb1c853769101852422b620883.json: -------------------------------------------------------------------------------- 1 | {"files":{"rails-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"rails.png","mtime":"2013-03-06T18:36:31-08:00","size":6646,"digest":"231a680f23887d9dd70710ea5efd3c62"},"application-eeb856e3fe2c8f879c91d0e81d59cb40.js":{"logical_path":"application.js","mtime":"2013-09-06T09:23:56-07:00","size":363970,"digest":"eeb856e3fe2c8f879c91d0e81d59cb40"},"application-4962059d8f80f9bb096692bacc29c4e8.css":{"logical_path":"application.css","mtime":"2013-09-06T13:20:19-07:00","size":134254,"digest":"4962059d8f80f9bb096692bacc29c4e8"},"glyphicons-halflings-white-62b67d9edee3db90d18833087f848d6e.png":{"logical_path":"glyphicons-halflings-white.png","mtime":"2013-06-24T16:40:09-07:00","size":8777,"digest":"62b67d9edee3db90d18833087f848d6e"},"glyphicons-halflings-c806376f05e4ccabe2c5315a8e95667c.png":{"logical_path":"glyphicons-halflings.png","mtime":"2013-06-24T16:40:09-07:00","size":12799,"digest":"c806376f05e4ccabe2c5315a8e95667c"}},"assets":{"rails.png":"rails-231a680f23887d9dd70710ea5efd3c62.png","application.js":"application-eeb856e3fe2c8f879c91d0e81d59cb40.js","application.css":"application-4962059d8f80f9bb096692bacc29c4e8.css","glyphicons-halflings-white.png":"glyphicons-halflings-white-62b67d9edee3db90d18833087f848d6e.png","glyphicons-halflings.png":"glyphicons-halflings-c806376f05e4ccabe2c5315a8e95667c.png"}} -------------------------------------------------------------------------------- /public/assets/rails-231a680f23887d9dd70710ea5efd3c62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/public/assets/rails-231a680f23887d9dd70710ea5efd3c62.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/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 | -------------------------------------------------------------------------------- /spec/controllers/microposts_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe MicropostsController do 3 | let(:minor) { FactoryGirl.create :user, minor: true, profanity_count: 0 } 4 | let(:adult) { FactoryGirl.create :user, minor: false, profanity_count: 0 } 5 | let(:profanity_content) { "Dad is a poopface and fartface!" } 6 | let(:clean_content) { "Dad is the best!" } 7 | 8 | def flash_now 9 | flash.instance_variable_get(:@now) 10 | end 11 | 12 | context "does contain two word profanity" do 13 | subject(:service) { MicropostCreationService.new(minor, profanity_content) } 14 | context "a minor" do 15 | before { sign_in minor, no_capybara: true } 16 | 17 | it "gives a flash with poopface and fartface" do 18 | post :create, micropost: { content: profanity_content } 19 | 20 | # Demonstrate testing flash.now 21 | expect(flash_now[:error]).to match /poopface/ 22 | expect(flash_now[:error]).to match /fartface/ 23 | expect(flash_now[:error]).to match /2 times/ 24 | 25 | # Demonstrate checking rendered template 26 | expect(response).to render_template('static_pages/home') 27 | 28 | # Demonstrate checking instance variable 29 | expect(assigns(:micropost).content).to eq(profanity_content) 30 | 31 | expect(response.status).to eq(200) 32 | end 33 | 34 | it "increases the minor's profanity count by 2" do 35 | expect do 36 | post :create, micropost: { content: profanity_content } 37 | end.to change { minor.reload.profanity_count }.by(2) 38 | end 39 | 40 | it "does not increase the number of posts" do 41 | expect do 42 | post :create, micropost: { content: profanity_content } 43 | end.not_to change { Micropost.count } 44 | end 45 | end 46 | 47 | context "adult" do 48 | before { sign_in adult, no_capybara: true } 49 | 50 | it "gives a flash with poopface and fartface" do 51 | post :create, micropost: { content: profanity_content } 52 | expect(flash[:success]).to eq("Micropost created!") 53 | expect(response).to redirect_to(root_url) 54 | end 55 | 56 | it "does not change the profanity count" do 57 | expect do 58 | post :create, micropost: { content: profanity_content } 59 | end.not_to change { adult.reload.profanity_count } 60 | end 61 | 62 | it "does increase the number of posts" do 63 | expect do 64 | post :create, micropost: { content: profanity_content } 65 | end.to change { Micropost.count }.by(1) 66 | end 67 | end 68 | end 69 | context "no profanity" do 70 | context "minor" do 71 | before { sign_in minor, no_capybara: true } 72 | 73 | it "gives a flash with poopface and fartface" do 74 | post :create, micropost: { content: clean_content } 75 | expect(flash[:success]).to eq("Micropost created!") 76 | expect(response).to redirect_to(root_url) 77 | end 78 | 79 | it "does not change the profanity count" do 80 | expect do 81 | post :create, micropost: { content: clean_content } 82 | end.not_to change { minor.reload.profanity_count } 83 | end 84 | 85 | it "does increase the number of posts" do 86 | expect do 87 | post :create, micropost: { content: clean_content } 88 | end.to change { Micropost.count }.by(1) 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /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, no_capybara: true } 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 | expect(response).to 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) do 28 | user.relationships.find_by(followed_id: other_user.id) 29 | end 30 | 31 | it "should decrement the Relationship count" do 32 | expect do 33 | xhr :delete, :destroy, id: relationship.id 34 | end.to change(Relationship, :count).by(-1) 35 | end 36 | 37 | it "should respond with success" do 38 | xhr :delete, :destroy, id: relationship.id 39 | expect(response).to be_success 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /spec/decorators/micropost_decorator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MicropostDecorator do 4 | 5 | describe "posted_ago" do 6 | let(:user) { FactoryGirl.create(:user) } 7 | it "prints time ago in words" do 8 | micropost = Micropost.create(content: "Lorem ipsum", user_id: user.id) 9 | expect(micropost.decorate.posted_ago).to match /Posted less than a minute ago./ 10 | end 11 | end 12 | 13 | describe "profanity_violation_msg" do 14 | let(:user) { FactoryGirl.create(:user, profanity_count: 20) } 15 | it "creates a message" do 16 | micropost = Micropost.create(content: "Lorem poopface, fartface", user_id: user.id) 17 | profanity_violation_msg = micropost.decorate.profanity_violation_msg 18 | expect(profanity_violation_msg).to match /poopface/ 19 | expect(profanity_violation_msg).to match /fartface/ 20 | expect(profanity_violation_msg).to match /20 times/ 21 | expect(profanity_violation_msg).to match /Your parents have been notified!/ 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /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 18 | -------------------------------------------------------------------------------- /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 title" do 7 | expect(full_title("foo")).to match(/foo/) 8 | end 9 | 10 | it "should include the base title" do 11 | expect(full_title("foo")).to match(/^Kid Blogger/) 12 | end 13 | 14 | it "should not include a bar for the home page" do 15 | expect(full_title("")).not_to match(/\|/) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/models/concerns/emailable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | 4 | describe User do 5 | 6 | before do 7 | @user = User.new(name: "Example User", email: "user@example.com", 8 | password: "foobar", password_confirmation: "foobar") 9 | end 10 | 11 | subject { @user } 12 | 13 | describe "#email_domain" do 14 | it "should equal example.com" do 15 | expect(@user.email_domain).to eq("example.com") 16 | end 17 | end 18 | 19 | describe "Email Validation" do 20 | describe "when email is not present" do 21 | before { @user.email = " " } 22 | it { should_not be_valid } 23 | end 24 | 25 | describe "when email format is invalid" do 26 | it "should be invalid" do 27 | addresses = %w[user@foo,com user_at_foo.org example.user@foo. 28 | foo@bar_baz.com foo@bar+baz.com foo@bar..com] 29 | addresses.each do |invalid_address| 30 | @user.email = invalid_address 31 | expect(@user).not_to be_valid 32 | end 33 | end 34 | end 35 | 36 | describe "when email format is valid" do 37 | it "should be valid" do 38 | addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn] 39 | addresses.each do |valid_address| 40 | @user.email = valid_address 41 | expect(@user).to be_valid 42 | end 43 | end 44 | end 45 | 46 | describe "when email address is already taken" do 47 | before do 48 | user_with_same_email = @user.dup 49 | user_with_same_email.email = @user.email.upcase 50 | user_with_same_email.save 51 | end 52 | 53 | it { should_not be_valid } 54 | end 55 | end 56 | end 57 | 58 | describe "User email queries" do 59 | before do 60 | @u1 = FactoryGirl.create(:user, email: "foobar@baz.com") 61 | @u2 = FactoryGirl.create(:user, email: "foobaz@bar.com") 62 | @u3 = FactoryGirl.create(:user, email: "abcdef@ghi.com") 63 | end 64 | 65 | describe ".by_email" do 66 | context "Using lower case" do 67 | it "finds the correct user" do 68 | expect(User.by_email(@u2.email).id).to eq(@u2.id) 69 | end 70 | end 71 | 72 | context "Using wrong case" do 73 | it "finds the correct user" do 74 | expect(User.by_email(@u1.email.upcase).id).to eq(@u1.id) 75 | end 76 | end 77 | 78 | context "Using no user" do 79 | it "finds no user" do 80 | expect(User.by_email("junk")).to be_nil 81 | end 82 | end 83 | end 84 | 85 | describe ".by_email_wildcard" do 86 | context "Using lower case" do 87 | it "finds the correct user" do 88 | expect(User.by_email_wildcard("foo").pluck(:id)).to match_array([@u1.id, @u2.id]) 89 | end 90 | end 91 | 92 | context "Using wrong case" do 93 | it "finds the correct user" do 94 | expect(User.by_email_wildcard("FoO").pluck(:id)).to match_array([@u1.id, @u2.id]) 95 | end 96 | end 97 | 98 | context "Using no match" do 99 | it "finds the correct user" do 100 | expect(User.by_email_wildcard("junk")).to be_empty 101 | end 102 | end 103 | end 104 | end 105 | 106 | -------------------------------------------------------------------------------- /spec/models/micropost_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: microposts 4 | # 5 | # id :integer not null, primary key 6 | # content :string(255) 7 | # user_id :integer 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | require 'spec_helper' 13 | 14 | describe Micropost do 15 | 16 | let(:user) { FactoryGirl.create(:user) } 17 | before do 18 | # This code is not idiomatically correct. 19 | @micropost = Micropost.new(content: "Lorem ipsum", user_id: user.id) 20 | end 21 | 22 | subject { @micropost } 23 | 24 | it { should respond_to(:content) } 25 | it { should respond_to(:user_id) } 26 | 27 | describe "when user_id is not present" do 28 | before { @micropost.user_id = nil } 29 | it { should_not be_valid } 30 | end 31 | 32 | describe "with blank content" do 33 | before { @micropost.content = " " } 34 | it { should_not be_valid } 35 | end 36 | 37 | describe "with content that is too long" do 38 | before { @micropost.content = "a" * 141 } 39 | it { should_not be_valid } 40 | end 41 | end 42 | 43 | describe "profanity checking" do 44 | let(:profane_content) { "Dad is a poopface and fartface!" } 45 | let(:clean_content) { "Dad is the best!" } 46 | let(:minor) { FactoryGirl.create :user, minor: true } 47 | let(:adult) { FactoryGirl.create :user, minor: false } 48 | context "user is a minor" do 49 | context "with profanity" do 50 | before { @micropost = Micropost.new(content: profane_content, user: minor) } 51 | it "returns false" do 52 | expect(@micropost).not_to be_valid 53 | end 54 | it "has validation error for content" do 55 | @micropost.save 56 | content_errors = @micropost.errors[:content][0] 57 | expect(content_errors).to match /poopface/ 58 | expect(content_errors).to match /fartface/ 59 | end 60 | end 61 | context "without profanity" do 62 | it "is valid" do 63 | micropost = Micropost.new(content: clean_content, user: minor) 64 | expect(micropost).to be_valid 65 | end 66 | end 67 | end 68 | context "user is an adult" do 69 | context "with profanity" do 70 | it "is valid" do 71 | micropost = Micropost.new(content: profane_content, user: adult) 72 | expect(micropost).to be_valid 73 | end 74 | end 75 | context "without profanity" do 76 | it "is valid" do 77 | micropost = Micropost.new(content: clean_content, user: adult) 78 | expect(micropost).to be_valid 79 | end 80 | end 81 | end 82 | end 83 | 84 | describe "Micropost#save_with_profanity_callbacks" do 85 | let(:profanity_content) { "Dad is a poopface and fartface!" } 86 | let(:clean_content) { "Dad is the best!" } 87 | let(:minor) { FactoryGirl.create :user, minor: true, profanity_count: 0 } 88 | let(:adult) { FactoryGirl.create :user, minor: false } 89 | 90 | context "user is a minor" do 91 | context "with profanity" do 92 | before { @micropost = Micropost.new(content: profanity_content, user: minor) } 93 | it "returns false" do 94 | expect(@micropost.save_with_profanity_callbacks).to be_false 95 | end 96 | 97 | it "has profanity validation errors" do 98 | @micropost.save_with_profanity_callbacks 99 | expect(@micropost.profanity_validation_error?).to be_true 100 | expect(@micropost).not_to be_persisted 101 | end 102 | 103 | it "has profanity updated the user's profanity count" do 104 | @micropost.save_with_profanity_callbacks 105 | expect(@micropost.user.profanity_count).to eq(2) 106 | expect(@micropost.user).to be_persisted 107 | end 108 | end 109 | 110 | context "without profanity" do 111 | before { @micropost = Micropost.new(content: clean_content, user: minor) } 112 | 113 | it "returns true" do 114 | expect(@micropost.save_with_profanity_callbacks).to be_true 115 | end 116 | 117 | it "returns saves the @micropost" do 118 | @micropost.save_with_profanity_callbacks 119 | expect(@micropost).to be_persisted 120 | expect(@micropost.user.profanity_count).to eq(0) 121 | end 122 | end 123 | end 124 | 125 | context "user is an adult" do 126 | context "with profanity" do 127 | before { @micropost = Micropost.new(content: profanity_content, user: adult) } 128 | 129 | it "returns true" do 130 | expect(@micropost.save_with_profanity_callbacks).to be_true 131 | end 132 | 133 | it "returns saves the @micropost" do 134 | @micropost.save_with_profanity_callbacks 135 | expect(@micropost).to be_persisted 136 | end 137 | end 138 | end 139 | end 140 | 141 | -------------------------------------------------------------------------------- /spec/models/relationship_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: relationships 4 | # 5 | # id :integer not null, primary key 6 | # follower_id :integer 7 | # followed_id :integer 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | require 'spec_helper' 13 | 14 | describe Relationship do 15 | 16 | let(:follower) { FactoryGirl.create(:user) } 17 | let(:followed) { FactoryGirl.create(:user) } 18 | let(:relationship) { follower.relationships.build(followed_id: followed.id) } 19 | 20 | subject { relationship } 21 | 22 | it { should be_valid } 23 | 24 | describe "follower methods" do 25 | it { should respond_to(:follower) } 26 | it { should respond_to(:followed) } 27 | its(:follower) { should eq follower } 28 | its(:followed) { should eq followed } 29 | end 30 | 31 | describe "when followed id is not present" do 32 | before { relationship.followed_id = nil } 33 | it { should_not be_valid } 34 | end 35 | 36 | describe "when follower id is not present" do 37 | before { relationship.follower_id = nil } 38 | it { should_not be_valid } 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # email :string(255) 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # password_digest :string(255) 11 | # remember_token :string(255) 12 | # admin :boolean 13 | # profanity_count :integer default(0) 14 | # minor :boolean default(FALSE) 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe User do 20 | 21 | before do 22 | @user = User.new(name: "Example User", email: "user@example.com", 23 | password: "foobar", password_confirmation: "foobar") 24 | end 25 | 26 | subject { @user } 27 | 28 | it { should respond_to(:name) } 29 | it { should respond_to(:email) } 30 | it { should respond_to(:password_digest) } 31 | it { should respond_to(:password) } 32 | it { should respond_to(:password_confirmation) } 33 | it { should respond_to(:remember_token) } 34 | it { should respond_to(:authenticate) } 35 | it { should respond_to(:admin) } 36 | it { should respond_to(:microposts) } 37 | it { should respond_to(:feed) } 38 | it { should respond_to(:relationships) } 39 | it { should respond_to(:followed_users) } 40 | it { should respond_to(:reverse_relationships) } 41 | it { should respond_to(:followers) } 42 | it { should respond_to(:following?) } 43 | it { should respond_to(:follow!) } 44 | it { should respond_to(:unfollow!) } 45 | 46 | it { should be_valid } 47 | it { should_not be_admin } 48 | 49 | describe "with admin attribute set to 'true'" do 50 | before do 51 | @user.save! 52 | @user.toggle!(:admin) 53 | end 54 | 55 | it { should be_admin } 56 | end 57 | 58 | describe "when name is not present" do 59 | before { @user.name = " " } 60 | it { should_not be_valid } 61 | end 62 | 63 | describe "when name is too long" do 64 | before { @user.name = "a" * 51 } 65 | it { should_not be_valid } 66 | end 67 | 68 | describe "when password is not present" do 69 | before do 70 | @user = User.new(name: "Example User", email: "user@example.com", 71 | password: " ", password_confirmation: " ") 72 | end 73 | it { should_not be_valid } 74 | end 75 | 76 | describe "when password doesn't match confirmation" do 77 | before { @user.password_confirmation = "mismatch" } 78 | it { should_not be_valid } 79 | end 80 | 81 | describe "with a password that's too short" do 82 | before { @user.password = @user.password_confirmation = "a" * 5 } 83 | it { should be_invalid } 84 | end 85 | 86 | describe "return value of authenticate method" do 87 | before { @user.save } 88 | let(:found_user) { User.find_by(email: @user.email) } 89 | 90 | describe "with valid password" do 91 | it { should eq found_user.authenticate(@user.password) } 92 | end 93 | 94 | describe "with invalid password" do 95 | let(:user_for_invalid_password) { found_user.authenticate("invalid") } 96 | 97 | it { should_not eq user_for_invalid_password } 98 | specify { expect(user_for_invalid_password).to be_false } 99 | end 100 | end 101 | 102 | describe "remember token" do 103 | before { @user.save } 104 | its(:remember_token) { should_not be_blank } 105 | end 106 | 107 | describe "micropost associations" do 108 | 109 | before { @user.save } 110 | let!(:older_micropost) do 111 | FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago) 112 | end 113 | let!(:newer_micropost) do 114 | FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago) 115 | end 116 | 117 | it "should have the right microposts in the right order" do 118 | expect(@user.microposts.to_a).to eq [newer_micropost, older_micropost] 119 | end 120 | 121 | it "should destroy associated microposts" do 122 | microposts = @user.microposts.to_a 123 | @user.destroy 124 | expect(microposts).not_to be_empty 125 | microposts.each do |micropost| 126 | expect(Micropost.where(id: micropost.id)).to be_empty 127 | end 128 | end 129 | 130 | describe "status" do 131 | let(:unfollowed_post) do 132 | FactoryGirl.create(:micropost, user: FactoryGirl.create(:user)) 133 | end 134 | let(:followed_user) { FactoryGirl.create(:user) } 135 | 136 | before do 137 | @user.follow!(followed_user) 138 | 3.times { followed_user.microposts.create!(content: "Lorem ipsum") } 139 | end 140 | 141 | its(:feed) { should include(newer_micropost) } 142 | its(:feed) { should include(older_micropost) } 143 | its(:feed) { should_not include(unfollowed_post) } 144 | its(:feed) do 145 | followed_user.microposts.each do |micropost| 146 | should include(micropost) 147 | end 148 | end 149 | end 150 | end 151 | 152 | describe "following" do 153 | let(:other_user) { FactoryGirl.create(:user) } 154 | before do 155 | @user.save 156 | @user.follow!(other_user) 157 | end 158 | 159 | it { should be_following(other_user) } 160 | its(:followed_users) { should include(other_user) } 161 | 162 | describe "followed user" do 163 | subject { other_user } 164 | its(:followers) { should include(@user) } 165 | end 166 | 167 | describe "and unfollowing" do 168 | before { @user.unfollow!(other_user) } 169 | 170 | it { should_not be_following(other_user) } 171 | its(:followed_users) { should_not include(other_user) } 172 | end 173 | end 174 | end 175 | 176 | describe "User#update_for_using_profanity" do 177 | let(:profanity_words) { ['poop', 'poopface'] } 178 | context "minor" do 179 | before { @minor = FactoryGirl.create(:user, minor: true) } 180 | it "increments the profanity counter" do 181 | expect do 182 | @minor.update_for_using_profanity(profanity_words) 183 | end.to change(@minor, :profanity_count).by(profanity_words.size) 184 | end 185 | it "sends parent notification of profanity" do 186 | allow(@minor).to receive(:send_parent_notification_of_profanity).and_return(nil) 187 | @minor.update_for_using_profanity(profanity_words) 188 | expect(@minor).to have_received(:send_parent_notification_of_profanity) 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /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_content('Sign in') } 11 | it { should have_title('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_title('Sign in') } 21 | it { should have_error_message('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_title(user.name) } 38 | it { should have_link('Users', href: users_path) } 39 | it { should have_link('Profile', href: user_path(user)) } 40 | it { should have_link('Settings', href: edit_user_path(user)) } 41 | it { should have_link('Sign out', href: signout_path) } 42 | it { should_not have_link('Sign in', href: signin_path) } 43 | 44 | describe "followed by signout" do 45 | before { click_link "Sign out" } 46 | it { should have_link('Sign in') } 47 | end 48 | end 49 | end 50 | 51 | describe "authorization" do 52 | 53 | describe "for non-signed-in users" do 54 | let(:user) { FactoryGirl.create(:user) } 55 | 56 | describe "when attempting to visit a protected page" do 57 | before do 58 | visit edit_user_path(user) 59 | fill_in "Email", with: user.email 60 | fill_in "Password", with: user.password 61 | click_button "Sign in" 62 | end 63 | 64 | describe "after signing in" do 65 | 66 | it "should render the desired protected page" do 67 | expect(page).to have_title('Edit user') 68 | end 69 | end 70 | end 71 | 72 | describe "in the Users controller" do 73 | 74 | describe "visiting the edit page" do 75 | before { visit edit_user_path(user) } 76 | it { should have_title('Sign in') } 77 | end 78 | 79 | describe "submitting to the update action" do 80 | before { patch user_path(user) } 81 | specify { expect(response).to redirect_to(signin_path) } 82 | end 83 | 84 | describe "visiting the user index" do 85 | before { visit users_path } 86 | it { should have_title('Sign in') } 87 | end 88 | 89 | describe "visiting the following page" do 90 | before { visit following_user_path(user) } 91 | it { should have_title('Sign in') } 92 | end 93 | 94 | describe "visiting the followers page" do 95 | before { visit followers_user_path(user) } 96 | it { should have_title('Sign in') } 97 | end 98 | end 99 | 100 | describe "in the Microposts controller" do 101 | 102 | describe "submitting to the create action" do 103 | before { post microposts_path } 104 | specify { expect(response).to redirect_to(signin_path) } 105 | end 106 | 107 | describe "submitting to the destroy action" do 108 | before { delete micropost_path(FactoryGirl.create(:micropost)) } 109 | specify { expect(response).to redirect_to(signin_path) } 110 | end 111 | end 112 | 113 | describe "in the Relationships controller" do 114 | describe "submitting to the create action" do 115 | before { post relationships_path } 116 | specify { expect(response).to redirect_to(signin_path) } 117 | end 118 | 119 | describe "submitting to the destroy action" do 120 | before { delete relationship_path(1) } 121 | specify { expect(response).to redirect_to(signin_path) } 122 | end 123 | end 124 | end 125 | 126 | describe "as wrong user" do 127 | let(:user) { FactoryGirl.create(:user) } 128 | let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") } 129 | before { sign_in user, no_capybara: true } 130 | 131 | describe "submitting a GET request to the Users#edit action" do 132 | before { get edit_user_path(wrong_user) } 133 | specify { expect(response.body).not_to match(full_title('Edit user')) } 134 | specify { expect(response).to redirect_to(root_url) } 135 | end 136 | 137 | describe "submitting a PATCH request to the Users#update action" do 138 | before { patch user_path(wrong_user) } 139 | specify { expect(response).to redirect_to(root_url) } 140 | end 141 | end 142 | 143 | describe "as non-admin user" do 144 | let(:user) { FactoryGirl.create(:user) } 145 | let(:non_admin) { FactoryGirl.create(:user) } 146 | 147 | before { sign_in non_admin, no_capybara: true } 148 | 149 | describe "submitting a DELETE request to the Users#destroy action" do 150 | before { delete user_path(user) } 151 | specify { expect(response).to redirect_to(root_url) } 152 | end 153 | end 154 | end 155 | 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 46 | -------------------------------------------------------------------------------- /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_content('Kid Safe Microblogging App') } 11 | it { should have_title(full_title('')) } 12 | it { should_not have_title('| 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") 18 | FactoryGirl.create(:micropost, user: user, content: "Ipsum") 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 | expect(page).to 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_content('Help') } 46 | it { should have_title(full_title('Help')) } 47 | end 48 | 49 | describe "About page" do 50 | before { visit about_path } 51 | 52 | it { should have_content('About') } 53 | it { should have_title(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_title(full_title('Contact')) } 61 | end 62 | 63 | 64 | end 65 | -------------------------------------------------------------------------------- /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 | let(:user) { FactoryGirl.create(:user) } 9 | before(:each) do 10 | sign_in user 11 | visit users_path 12 | end 13 | 14 | it { should have_title('All users') } 15 | it { should have_content('All users') } 16 | 17 | describe "pagination" do 18 | 19 | before(:all) { 30.times { FactoryGirl.create(:user) } } 20 | after(:all) { User.delete_all } 21 | 22 | it { should have_selector('div.pagination') } 23 | 24 | it "should list each user" do 25 | User.paginate(page: 1).each do |user| 26 | expect(page).to have_selector('li', text: user.name) 27 | end 28 | end 29 | end 30 | 31 | describe "delete links" do 32 | 33 | it { should_not have_link('delete') } 34 | 35 | describe "as an admin user" do 36 | let(:admin) { FactoryGirl.create(:admin) } 37 | before do 38 | sign_in admin 39 | visit users_path 40 | end 41 | 42 | it { should have_link('delete', href: user_path(User.first)) } 43 | it "should be able to delete another user" do 44 | expect do 45 | click_link('delete', match: :first) 46 | end.to change(User, :count).by(-1) 47 | end 48 | it { should_not have_link('delete', href: user_path(admin)) } 49 | end 50 | end 51 | end 52 | 53 | describe "profile page" do 54 | let(:user) { FactoryGirl.create(:user) } 55 | let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") } 56 | let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") } 57 | 58 | before { visit user_path(user) } 59 | 60 | it { should have_content(user.name) } 61 | it { should have_title(user.name) } 62 | 63 | describe "microposts" do 64 | it { should have_content(m1.content) } 65 | it { should have_content(m2.content) } 66 | it { should have_content(user.microposts.count) } 67 | end 68 | 69 | describe "follow/unfollow buttons" do 70 | let(:other_user) { FactoryGirl.create(:user) } 71 | before { sign_in user } 72 | 73 | describe "following a user" do 74 | before { visit user_path(other_user) } 75 | 76 | it "should increment the followed user count" do 77 | expect do 78 | click_button "Follow" 79 | end.to change(user.followed_users, :count).by(1) 80 | end 81 | 82 | it "should increment the other user's followers count" do 83 | expect do 84 | click_button "Follow" 85 | end.to change(other_user.followers, :count).by(1) 86 | end 87 | 88 | describe "toggling the button" do 89 | before { click_button "Follow" } 90 | it { should have_xpath("//input[@value='Unfollow']") } 91 | end 92 | end 93 | 94 | describe "unfollowing a user" do 95 | before do 96 | user.follow!(other_user) 97 | visit user_path(other_user) 98 | end 99 | 100 | it "should decrement the followed user count" do 101 | expect do 102 | click_button "Unfollow" 103 | end.to change(user.followed_users, :count).by(-1) 104 | end 105 | 106 | it "should decrement the other user's followers count" do 107 | expect do 108 | click_button "Unfollow" 109 | end.to change(other_user.followers, :count).by(-1) 110 | end 111 | 112 | describe "toggling the button" do 113 | before { click_button "Unfollow" } 114 | it { should have_xpath("//input[@value='Follow']") } 115 | end 116 | end 117 | end 118 | end 119 | 120 | describe "signup page" do 121 | before { visit signup_path } 122 | 123 | it { should have_content('Sign up') } 124 | it { should have_title(full_title('Sign up')) } 125 | end 126 | 127 | describe "signup" do 128 | 129 | before { visit signup_path } 130 | 131 | let(:submit) { "Create my account" } 132 | 133 | describe "with invalid information" do 134 | it "should not create a user" do 135 | expect { click_button submit }.not_to change(User, :count) 136 | end 137 | end 138 | 139 | describe "with valid information" do 140 | before do 141 | fill_in "Name", with: "Example User" 142 | fill_in "Email", with: "user@example.com" 143 | fill_in "Password", with: "foobar" 144 | fill_in "Confirmation", with: "foobar" 145 | end 146 | 147 | it "should create a user" do 148 | expect { click_button submit }.to change(User, :count).by(1) 149 | end 150 | 151 | describe "after saving the user" do 152 | before { click_button submit } 153 | let(:user) { User.find_by(email: 'user@example.com') } 154 | 155 | it { should have_link('Sign out') } 156 | it { should have_title(user.name) } 157 | it { should have_selector('div.alert.alert-success', text: 'Welcome') } 158 | end 159 | end 160 | end 161 | 162 | describe "edit" do 163 | let(:user) { FactoryGirl.create(:user) } 164 | before do 165 | sign_in user 166 | visit edit_user_path(user) 167 | end 168 | 169 | describe "page" do 170 | it { should have_content("Update your profile") } 171 | it { should have_title("Edit user") } 172 | it { should have_link('change', href: 'http://gravatar.com/emails') } 173 | end 174 | 175 | describe "with invalid information" do 176 | before { click_button "Save changes" } 177 | 178 | it { should have_content('error') } 179 | end 180 | 181 | describe "with valid information" do 182 | let(:new_name) { "New Name" } 183 | let(:new_email) { "new@example.com" } 184 | before do 185 | fill_in "Name", with: new_name 186 | fill_in "Email", with: new_email 187 | fill_in "Password", with: user.password 188 | fill_in "Confirm Password", with: user.password 189 | click_button "Save changes" 190 | end 191 | 192 | it { should have_title(new_name) } 193 | it { should have_selector('div.alert.alert-success') } 194 | it { should have_link('Sign out', href: signout_path) } 195 | specify { expect(user.reload.name).to eq new_name } 196 | specify { expect(user.reload.email).to eq new_email } 197 | end 198 | 199 | describe "forbidden attributes" do 200 | let(:params) do 201 | { user: { admin: true, password: user.password, 202 | password_confirmation: user.password } } 203 | end 204 | before do 205 | sign_in user, no_capybara: true 206 | patch user_path(user), params 207 | end 208 | specify { expect(user.reload).not_to be_admin } 209 | end 210 | end 211 | 212 | describe "following/followers" do 213 | let(:user) { FactoryGirl.create(:user) } 214 | let(:other_user) { FactoryGirl.create(:user) } 215 | before { user.follow!(other_user) } 216 | 217 | describe "followed users" do 218 | before do 219 | sign_in user 220 | visit following_user_path(user) 221 | end 222 | 223 | it { should have_title(full_title('Following')) } 224 | it { should have_selector('h3', text: 'Following') } 225 | it { should have_link(other_user.name, href: user_path(other_user)) } 226 | end 227 | 228 | describe "followers" do 229 | before do 230 | sign_in other_user 231 | visit followers_user_path(other_user) 232 | end 233 | 234 | it { should have_title(full_title('Followers')) } 235 | it { should have_selector('h3', text: 'Followers') } 236 | it { should have_link(user.name, href: user_path(user)) } 237 | end 238 | end 239 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spork' 3 | 4 | Spork.prefork do 5 | ENV["RAILS_ENV"] ||= 'test' 6 | require File.expand_path("../../config/environment", __FILE__) 7 | require 'rspec/rails' 8 | require 'rspec/autorun' 9 | require 'draper/test/rspec_integration' 10 | 11 | # Requires supporting ruby files with custom matchers and macros, etc, 12 | # in spec/support/ and its subdirectories. 13 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 14 | 15 | # Checks for pending migrations before tests are run. 16 | # If you are not using ActiveRecord, you can remove this line. 17 | ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) 18 | 19 | RSpec.configure do |config| 20 | # ## Mock Framework 21 | # 22 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 23 | # 24 | # config.mock_with :mocha 25 | # config.mock_with :flexmock 26 | # config.mock_with :rr 27 | 28 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 29 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 30 | 31 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 32 | # examples within a transaction, remove the following line or assign false 33 | # instead of true. 34 | config.use_transactional_fixtures = true 35 | 36 | # If true, the base class of anonymous controllers will be inferred 37 | # automatically. This will be the default behavior in future versions of 38 | # rspec-rails. 39 | config.infer_base_class_for_anonymous_controllers = false 40 | 41 | # Run specs in random order to surface order dependencies. If you find an 42 | # order dependency and want to debug it, you can fix the order by providing 43 | # the seed, which is printed after each run. 44 | # --seed 1234 45 | config.order = "random" 46 | # Include the Capybara DSL so that specs in spec/requests still work. 47 | config.include Capybara::DSL 48 | # Disable the old-style object.should syntax. 49 | config.expect_with :rspec do |c| 50 | c.syntax = :expect 51 | end 52 | end 53 | end 54 | 55 | Spork.each_run do 56 | # This code will be run each time you run your specs. 57 | 58 | end 59 | -------------------------------------------------------------------------------- /spec/support/utilities.rb: -------------------------------------------------------------------------------- 1 | include ApplicationHelper 2 | 3 | def sign_in(user, options={}) 4 | if options[:no_capybara] 5 | # Sign in when not using Capybara as well. 6 | remember_token = User.new_remember_token 7 | cookies[:remember_token] = remember_token 8 | user.update_attribute(:remember_token, User.hash(remember_token)) 9 | else 10 | visit signin_path 11 | fill_in "Email", with: user.email 12 | fill_in "Password", with: user.password 13 | click_button "Sign in" 14 | end 15 | end 16 | 17 | RSpec::Matchers.define :have_error_message do |message| 18 | match do |page| 19 | expect(page).to have_selector('div.alert.alert-error', text: message) 20 | end 21 | end -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakacode/fat-code-refactoring-techniques/a578449db549b4d367d354d2f94f1fd5c5bb5f8d/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------