Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

major redesign

  • Loading branch information...
commit 8668b8e2e0c59293b875bcda33952948a7e6dbc7 1 parent d124319
@ryanb authored
Showing with 2,425 additions and 2,737 deletions.
  1. +2 −0  .gitignore
  2. +1 −1  .rvmrc
  3. +18 −7 Gemfile
  4. +139 −79 Gemfile.lock
  5. +17 −0 Guardfile
  6. +1 −1  LICENSE
  7. +2 −2 README.markdown
  8. +7 −0 Rakefile
  9. +2 −23 app/controllers/application_controller.rb
  10. +23 −51 app/controllers/comments_controller.rb
  11. +15 −34 app/controllers/episodes_controller.rb
  12. +15 −0 app/controllers/feedback_messages_controller.rb
  13. +0 −6 app/controllers/info_controller.rb
  14. +0 −39 app/controllers/spam_checks_controller.rb
  15. +0 −39 app/controllers/spam_questions_controller.rb
  16. +0 −34 app/controllers/spam_reports_controller.rb
  17. +0 −39 app/controllers/sponsors_controller.rb
  18. +0 −6 app/controllers/tags_controller.rb
  19. +11 −9 app/controllers/users_controller.rb
  20. +54 −1 app/helpers/application_helper.rb
  21. +12 −2 app/helpers/comments_helper.rb
  22. +0 −5 app/helpers/episodes_helper.rb
  23. +2 −0  app/helpers/feedback_messages_helper.rb
  24. +0 −3  app/helpers/sessions_helper.rb
  25. +0 −2  app/helpers/spam_checks_helper.rb
  26. +0 −2  app/helpers/spam_questions_helper.rb
  27. +0 −2  app/helpers/spam_reports_helper.rb
  28. +0 −2  app/helpers/sponsors_helper.rb
  29. +0 −3  app/helpers/tags_helper.rb
  30. +6 −0 app/mailers/mailer.rb
  31. +25 −0 app/models/ability.rb
  32. +2 −35 app/models/comment.rb
  33. +0 −3  app/models/download.rb
  34. +74 −44 app/models/episode.rb
  35. +4 −0 app/models/feedback_message.rb
  36. +0 −5 app/models/spam_check.rb
  37. +0 −7 app/models/spam_question.rb
  38. +0 −44 app/models/spam_report.rb
  39. +0 −11 app/models/sponsor.rb
  40. +4 −0 app/models/tag.rb
  41. +4 −0 app/models/user.rb
  42. +34 −35 app/views/comments/_comment.html.erb
  43. +17 −0 app/views/comments/_comment_headline.html.erb
  44. +69 −15 app/views/comments/_form.html.erb
  45. +10 −0 app/views/comments/create.js.erb
  46. +1 −1  app/views/comments/destroy.js.erb
  47. +1 −1  app/views/comments/edit.html.erb
  48. +2 −0  app/views/comments/edit.js.erb
  49. +0 −8 app/views/comments/index.html.erb
  50. +0 −19 app/views/comments/index.rss.builder
  51. +2 −12 app/views/comments/new.html.erb
  52. +2 −0  app/views/comments/new.js.erb
  53. +5 −0 app/views/comments/update.js.erb
  54. +4 −0 app/views/episodes/_comments.html.erb
  55. +10 −41 app/views/episodes/_episode.html.erb
  56. +22 −37 app/views/episodes/_form.html.erb
  57. +0 −5 app/views/episodes/_pagination.html.erb
  58. +14 −0 app/views/episodes/_show_notes.html.erb
  59. +5 −0 app/views/episodes/_similar.html.erb
  60. +0 −20 app/views/episodes/archive.html.erb
  61. +92 −28 app/views/episodes/index.html.erb
  62. +21 −25 app/views/episodes/index.rss.builder
  63. +52 −39 app/views/episodes/show.html.erb
  64. +1 −0  app/views/episodes/show.js.erb
  65. +0 −20 app/views/episodes/show.rss.builder
  66. +27 −0 app/views/feedback_messages/new.html.erb
  67. +12 −8 app/views/info/about.html.erb
  68. +0 −228 app/views/info/contest.html.erb
  69. +0 −24 app/views/info/feeds.html.erb
  70. +51 −28 app/views/layouts/application.html.erb
  71. +4 −0 app/views/mailer/feedback.text.erb
  72. +0 −24 app/views/shared/_navigation.html.erb
  73. +0 −44 app/views/shared/_side.html.erb
  74. +0 −12 app/views/spam_checks/_form.html.erb
  75. +0 −4 app/views/spam_checks/edit.html.erb
  76. +0 −22 app/views/spam_checks/index.html.erb
  77. +0 −5 app/views/spam_checks/new.html.erb
  78. +0 −12 app/views/spam_questions/_form.html.erb
  79. +0 −4 app/views/spam_questions/edit.html.erb
  80. +0 −22 app/views/spam_questions/index.html.erb
  81. +0 −5 app/views/spam_questions/new.html.erb
  82. +0 −1  app/views/spam_reports/create.js.erb
  83. +0 −27 app/views/spam_reports/index.html.erb
  84. +0 −36 app/views/spam_reports/show.html.erb
  85. +0 −26 app/views/sponsors/_form.html.erb
  86. +0 −3  app/views/sponsors/edit.html.erb
  87. +0 −16 app/views/sponsors/index.html.erb
  88. +0 −3  app/views/sponsors/new.html.erb
  89. +0 −7 app/views/tags/show.html.erb
  90. +1 −1  app/views/users/edit.html.erb
  91. +0 −2  autotest/discover.rb
  92. +1 −0  config/application.rb
  93. +0 −1  config/examples/app_config.yml
  94. +0 −1  config/initializers/load_enkoder.rb
  95. +3 −13 config/routes.rb
  96. +11 −0 db/migrate/20110416201115_add_ancestry_to_comments.rb
  97. +9 −0 db/migrate/20110416214833_add_legacy_to_episodes.rb
  98. +15 −0 db/migrate/20110416232852_create_feedback_messages.rb
  99. +59 −0 db/migrate/20110421060544_cleanup.rb
  100. +9 −0 db/migrate/20110423184218_add_legacy_to_comments.rb
  101. +9 −0 db/migrate/20110503025228_add_file_sizes_to_episodes.rb
  102. +9 −0 db/migrate/20110504180955_add_hidden_to_comments.rb
  103. +10 −42 db/schema.rb
  104. +57 −0 lib/code_formatter.rb
  105. +0 −144 lib/enkoder.rb
  106. +41 −0 lib/tasks/application.rake
  107. +0 −16 lib/textilizer.rb
  108. BIN  public/favicon.ico
  109. BIN  public/flash/clippy.swf
  110. BIN  public/images/guest.png
  111. BIN  public/images/icons/asciicasts.png
  112. BIN  public/images/icons/comments.png
  113. BIN  public/images/icons/facebook.png
  114. BIN  public/images/icons/itunes.png
  115. BIN  public/images/icons/new_comment.png
  116. BIN  public/images/icons/rss.png
  117. BIN  public/images/icons/show_notes.png
  118. BIN  public/images/icons/twitter.png
  119. BIN  public/images/progress_large.gif
  120. BIN  public/images/railscasts_logo.png
  121. BIN  public/images/ryan_bates.jpg
  122. BIN  public/images/sublimevideo.png
  123. BIN  public/images/views/full.png
  124. BIN  public/images/views/grid.png
  125. BIN  public/images/views/list.png
  126. +43 −10 public/javascripts/application.js
  127. +751 −195 public/stylesheets/application.css
  128. +3 −7 public/stylesheets/coderay.css
  129. +0 −94 spec/controllers/comments_controller_spec.rb
  130. +0 −126 spec/controllers/episodes_controller_spec.rb
  131. +0 −21 spec/controllers/info_controller_spec.rb
  132. +0 −63 spec/controllers/spam_checks_controller_spec.rb
  133. +0 −63 spec/controllers/spam_questions_controller_spec.rb
  134. +0 −60 spec/controllers/spam_reports_controller_spec.rb
  135. +0 −63 spec/controllers/sponsors_controller_spec.rb
  136. +0 −13 spec/controllers/tags_controller_spec.rb
  137. +0 −66 spec/controllers/users_controller_spec.rb
  138. +7 −7 spec/factories.rb
  139. +0 −19 spec/fixtures/comments.yml
  140. +0 −11 spec/fixtures/downloads.yml
  141. +0 −10 spec/fixtures/episodes.yml
  142. +0 −7 spec/fixtures/spam_checks.yml
  143. +0 −7 spec/fixtures/spam_questions.yml
  144. +0 −13 spec/fixtures/spam_reports.yml
  145. +0 −11 spec/fixtures/sponsors.yml
  146. +0 −7 spec/fixtures/taggings.yml
  147. +0 −5 spec/fixtures/tags.yml
  148. +0 −17 spec/fixtures/users.yml
  149. +11 −3 spec/helpers/comments_helper_spec.rb
  150. +42 −0 spec/lib/code_formatter_spec.rb
  151. +0 −31 spec/lib/textilizer_spec.rb
  152. +92 −0 spec/models/ability_spec.rb
  153. +5 −36 spec/models/comment_spec.rb
  154. +0 −4 spec/models/download_spec.rb
  155. +41 −17 spec/models/episode_spec.rb
  156. +10 −0 spec/models/feedback_message_spec.rb
  157. +0 −7 spec/models/spam_check_spec.rb
  158. +0 −7 spec/models/spam_question_spec.rb
  159. +0 −54 spec/models/spam_report_spec.rb
  160. +0 −16 spec/models/sponsor_spec.rb
  161. +1 −1  spec/models/tag_spec.rb
  162. +1 −1  spec/models/tagging_spec.rb
  163. +1 −1  spec/models/user_spec.rb
  164. +44 −0 spec/requests/comments_request_spec.rb
  165. +112 −0 spec/requests/episodes_request_spec.rb
  166. +15 −0 spec/requests/feedback_messages_request_spec.rb
  167. +8 −0 spec/requests/info_request_spec.rb
  168. +47 −0 spec/requests/users_request_spec.rb
  169. +53 −5 spec/spec_helper.rb
  170. +6 −0 spec/support/auth_macros.rb
  171. +0 −31 spec/support/controller_macros.rb
View
2  .gitignore
@@ -3,6 +3,8 @@ db/*.sqlite3
log/*.log
log/*.pid
tmp/**/*
+tmp/*
+coverage/*
config/database.yml
config/app_config.yml
config/*.sphinx.conf
View
2  .rvmrc
@@ -1 +1 @@
-rvm use 1.9.2-p0
+rvm use 1.9.2@railscasts
View
25 Gemfile
@@ -1,26 +1,37 @@
source 'http://rubygems.org'
-gem "rails", "3.0.3"
+gem "rails", "3.0.7"
gem "mysql2"
-gem "RedCloth"
+gem "redcarpet"
gem "coderay"
gem "acts_as_list"
gem "thinking-sphinx", ">= 2.0.1", :require => "thinking_sphinx"
gem "whenever", :require => false
gem "will_paginate", ">= 3.0.pre2"
gem "jquery-rails"
-gem "omniauth"
+gem "omniauth", ">= 0.2.2"
gem "exception_notification", :git => "git://github.com/rails/exception_notification.git", :require => "exception_notifier"
+gem "ancestry"
+gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0"
group :development, :test do
- gem "mocha"
gem "rspec-rails"
+ gem "launchy"
+end
+
+group :test do
gem "factory_girl_rails"
- gem "webrat"
- gem "autotest"
- gem "autotest-rails"
+ gem "capybara", :git => "git://github.com/jnicklas/capybara.git"
+ gem "capybara-webkit"
+ gem "database_cleaner"
+ gem "guard"
+ gem "guard-rspec"
+ gem "rb-fsevent", :require => false if RUBY_PLATFORM.downcase.include?("darwin")
+ gem "fakeweb"
+ gem "simplecov", :require => false
end
group :development do
+ gem "thin"
gem "nifty-generators"
end
View
218 Gemfile.lock
@@ -1,136 +1,177 @@
GIT
+ remote: git://github.com/jnicklas/capybara.git
+ revision: 4483061ccc32c34345e84685e8ed43870e7a8693
+ specs:
+ capybara (0.4.1.1)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (>= 0.0.27)
+ xpath (~> 0.1.3)
+
+GIT
remote: git://github.com/rails/exception_notification.git
revision: 192a49a02d63d28b23ed41cebadfedd490929cf1
specs:
exception_notification (1.0.0)
+GIT
+ remote: git://github.com/ryanb/cancan.git
+ revision: 63865cc7d8df9ea080e7fb1adf6ca8eeb1719ee9
+ branch: 2.0
+ specs:
+ cancan (1.6.3)
+
GEM
remote: http://rubygems.org/
specs:
- RedCloth (4.2.3)
- ZenTest (4.4.0)
aaronh-chronic (0.3.9)
abstract (1.0.0)
- actionmailer (3.0.3)
- actionpack (= 3.0.3)
- mail (~> 2.2.9)
- actionpack (3.0.3)
- activemodel (= 3.0.3)
- activesupport (= 3.0.3)
+ actionmailer (3.0.7)
+ actionpack (= 3.0.7)
+ mail (~> 2.2.15)
+ actionpack (3.0.7)
+ activemodel (= 3.0.7)
+ activesupport (= 3.0.7)
builder (~> 2.1.2)
erubis (~> 2.6.6)
- i18n (~> 0.4)
+ i18n (~> 0.5.0)
rack (~> 1.2.1)
- rack-mount (~> 0.6.13)
- rack-test (~> 0.5.6)
+ rack-mount (~> 0.6.14)
+ rack-test (~> 0.5.7)
tzinfo (~> 0.3.23)
- activemodel (3.0.3)
- activesupport (= 3.0.3)
+ activemodel (3.0.7)
+ activesupport (= 3.0.7)
builder (~> 2.1.2)
- i18n (~> 0.4)
- activerecord (3.0.3)
- activemodel (= 3.0.3)
- activesupport (= 3.0.3)
+ i18n (~> 0.5.0)
+ activerecord (3.0.7)
+ activemodel (= 3.0.7)
+ activesupport (= 3.0.7)
arel (~> 2.0.2)
tzinfo (~> 0.3.23)
- activeresource (3.0.3)
- activemodel (= 3.0.3)
- activesupport (= 3.0.3)
- activesupport (3.0.3)
+ activeresource (3.0.7)
+ activemodel (= 3.0.7)
+ activesupport (= 3.0.7)
+ activesupport (3.0.7)
acts_as_list (0.1.2)
- addressable (2.2.2)
- arel (2.0.3)
- autotest (4.4.1)
- autotest-rails (4.1.0)
- ZenTest
+ addressable (2.2.5)
+ ancestry (1.2.3)
+ activerecord (>= 2.2.2)
+ arel (2.0.9)
builder (2.1.2)
+ capybara-webkit (0.2.0)
+ capybara (~> 0.4.1)
+ childprocess (0.1.8)
+ ffi (~> 1.0.6)
coderay (0.9.5)
+ configuration (1.2.0)
+ daemons (1.1.2)
+ database_cleaner (0.6.7)
diff-lcs (1.1.2)
erubis (2.6.6)
abstract (>= 1.0.0)
+ eventmachine (0.12.10)
factory_girl (1.3.2)
factory_girl_rails (1.0)
factory_girl (~> 1.3)
rails (>= 3.0.0.beta4)
- faraday (0.5.3)
- addressable (~> 2.2.2)
- multipart-post (~> 1.0.1)
+ fakeweb (1.3.0)
+ faraday (0.6.1)
+ addressable (~> 2.2.4)
+ multipart-post (~> 1.1.0)
rack (>= 1.1.0, < 2)
- i18n (0.4.2)
+ ffi (1.0.7)
+ rake (>= 0.8.7)
+ guard (0.3.4)
+ thor (~> 0.14.6)
+ guard-rspec (0.3.1)
+ guard (>= 0.2.2)
+ i18n (0.5.0)
jquery-rails (0.2.5)
rails (~> 3.0)
thor (~> 0.14.4)
- mail (2.2.10)
+ json_pure (1.5.1)
+ launchy (0.4.0)
+ configuration (>= 0.0.5)
+ rake (>= 0.8.1)
+ mail (2.2.19)
activesupport (>= 2.3.6)
- i18n (~> 0.4.1)
+ i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.16)
- mocha (0.9.9)
- rake
multi_json (0.0.5)
- multipart-post (1.0.1)
+ multipart-post (1.1.0)
mysql2 (0.2.6)
net-ldap (0.1.1)
nifty-generators (0.4.2)
nokogiri (1.4.4)
- oa-basic (0.1.6)
+ oa-basic (0.2.2)
multi_json (~> 0.0.2)
nokogiri (~> 1.4.2)
- oa-core (= 0.1.6)
+ oa-core (= 0.2.2)
rest-client (~> 1.6.0)
- oa-core (0.1.6)
+ oa-core (0.2.2)
rack (~> 1.1)
- oa-enterprise (0.1.6)
+ oa-enterprise (0.2.2)
net-ldap (~> 0.1.1)
nokogiri (~> 1.4.2)
- oa-core (= 0.1.6)
+ oa-core (= 0.2.2)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
- oa-oauth (0.1.6)
+ oa-more (0.2.2)
+ multi_json (~> 0.0.2)
+ oa-core (= 0.2.2)
+ rest-client (~> 1.6.0)
+ oa-oauth (0.2.2)
+ faraday (~> 0.6.1)
multi_json (~> 0.0.2)
nokogiri (~> 1.4.2)
- oa-core (= 0.1.6)
+ oa-core (= 0.2.2)
oauth (~> 0.4.0)
- oauth2 (~> 0.1.0)
- oa-openid (0.1.6)
- oa-core (= 0.1.6)
+ oauth2 (~> 0.3.0)
+ oa-openid (0.2.2)
+ oa-core (= 0.2.2)
rack-openid (~> 1.2.0)
ruby-openid-apps-discovery
oauth (0.4.4)
- oauth2 (0.1.0)
- faraday (~> 0.5.0)
+ oauth2 (0.3.0)
+ faraday (~> 0.6.0)
multi_json (~> 0.0.4)
- omniauth (0.1.6)
- oa-basic (= 0.1.6)
- oa-core (= 0.1.6)
- oa-enterprise (= 0.1.6)
- oa-oauth (= 0.1.6)
- oa-openid (= 0.1.6)
+ omniauth (0.2.2)
+ oa-basic (= 0.2.2)
+ oa-core (= 0.2.2)
+ oa-enterprise (= 0.2.2)
+ oa-more (= 0.2.2)
+ oa-oauth (= 0.2.2)
+ oa-openid (= 0.2.2)
polyglot (0.3.1)
pyu-ruby-sasl (0.0.3.2)
- rack (1.2.1)
- rack-mount (0.6.13)
+ rack (1.2.2)
+ rack-mount (0.6.14)
rack (>= 1.0.0)
rack-openid (1.2.0)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
- rack-test (0.5.6)
+ rack-test (0.5.7)
rack (>= 1.0)
- rails (3.0.3)
- actionmailer (= 3.0.3)
- actionpack (= 3.0.3)
- activerecord (= 3.0.3)
- activeresource (= 3.0.3)
- activesupport (= 3.0.3)
+ rails (3.0.7)
+ actionmailer (= 3.0.7)
+ actionpack (= 3.0.7)
+ activerecord (= 3.0.7)
+ activeresource (= 3.0.7)
+ activesupport (= 3.0.7)
bundler (~> 1.0)
- railties (= 3.0.3)
- railties (3.0.3)
- actionpack (= 3.0.3)
- activesupport (= 3.0.3)
+ railties (= 3.0.7)
+ railties (3.0.7)
+ actionpack (= 3.0.7)
+ activesupport (= 3.0.7)
rake (>= 0.8.7)
thor (~> 0.14.4)
rake (0.8.7)
+ rb-fsevent (0.4.0)
+ redcarpet (1.10.0)
rest-client (1.6.1)
mime-types (>= 1.16)
riddle (1.2.1)
@@ -148,41 +189,60 @@ GEM
ruby-openid-apps-discovery (1.2.0)
ruby-openid (>= 2.1.7)
rubyntlm (0.1.1)
+ rubyzip (0.9.4)
+ selenium-webdriver (0.1.4)
+ childprocess (>= 0.1.7)
+ ffi (>= 1.0.7)
+ json_pure
+ rubyzip
+ simplecov (0.4.2)
+ simplecov-html (~> 0.4.4)
+ simplecov-html (0.4.4)
+ thin (1.2.11)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
thinking-sphinx (2.0.1)
activerecord (>= 3.0.3)
riddle (>= 1.2.1)
- thor (0.14.4)
+ thor (0.14.6)
treetop (1.4.9)
polyglot (>= 0.3.1)
- tzinfo (0.3.23)
- webrat (0.7.2)
- nokogiri (>= 1.2.0)
- rack (>= 1.0)
- rack-test (>= 0.5.3)
+ tzinfo (0.3.27)
whenever (0.6.2)
aaronh-chronic (>= 0.3.9)
activesupport (>= 2.3.4)
will_paginate (3.0.pre2)
+ xpath (0.1.3)
+ nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
- RedCloth
acts_as_list
- autotest
- autotest-rails
+ ancestry
+ cancan!
+ capybara!
+ capybara-webkit
coderay
+ database_cleaner
exception_notification!
factory_girl_rails
+ fakeweb
+ guard
+ guard-rspec
jquery-rails
- mocha
+ launchy
mysql2
nifty-generators
- omniauth
- rails (= 3.0.3)
+ omniauth (>= 0.2.2)
+ rails (= 3.0.7)
+ rb-fsevent
+ redcarpet
rspec-rails
+ simplecov
+ thin
thinking-sphinx (>= 2.0.1)
- webrat
whenever
will_paginate (>= 3.0.pre2)
View
17 Guardfile
@@ -0,0 +1,17 @@
+# A sample Guardfile
+# More info at http://github.com/guard/guard#readme
+
+guard 'rspec', :version => 2 do
+ watch('^spec/(.*)_spec.rb')
+ watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('^spec/spec_helper.rb') { "spec" }
+
+ # Rails example
+ watch('^app/views/(.*)/.*') { |m| "spec/requests/#{m[1]}_request_spec.rb" }
+ watch('^app/controllers/(.*)_controller\.rb') { |m| "spec/requests/#{m[1]}_request_spec.rb" }
+ watch('^app/(.*)\.rb') { |m| "spec/#{m[1]}_spec.rb" }
+ # watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('^config/routes.rb') { "spec/routing" }
+ watch('^app/controllers/application_controller.rb') { "spec/controllers" }
+ watch('^spec/factories.rb') { "spec/models" }
+end
View
2  LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010 Ryan Bates, Railscasts
+Copyright (c) 2011 Ryan Bates, RailsCasts
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
4 README.markdown
@@ -1,6 +1,6 @@
-# Railscasts
+# RailsCasts
-This is the source code for the [Railscasts site](http://railscasts.com/). If you want the source code for the episodes, see the [railscasts-episodes](http://github.com/ryanb/railscasts-episodes) project.
+This is the source code for the [RailsCasts site](http://railscasts.com/). If you want the source code for the episodes, see the [railscasts-episodes](http://github.com/ryanb/railscasts-episodes) project.
Please [let me know](https://github.com/inbox/new/ryanb) if you plan to use this app for your site.
View
7 Rakefile
@@ -4,4 +4,11 @@
require File.expand_path('../config/application', __FILE__)
require 'rake'
+desc "Run tests with coverage"
+task :coverage do
+ ENV['COVERAGE'] = "true"
+ Rake::Task["spec"].execute
+ Launchy.open("file://" + File.expand_path("../coverage/index.html", __FILE__))
+end
+
Railscasts::Application.load_tasks
View
25 app/controllers/application_controller.rb
@@ -1,34 +1,13 @@
class ApplicationController < ActionController::Base
protect_from_forgery
- helper_method :admin?, :current_user
+ enable_authorization
private
- def admin?
- current_user && current_user.admin?
- end
-
- def current_spam_question
- @current_spam_question ||= SpamQuestion.find(session[:spam_question_id]) if session[:spam_question_id]
- end
- helper_method :current_spam_question
-
def current_user
@current_user ||= User.find_by_token(cookies[:token]) if cookies[:token]
end
-
- def login_required
- unless current_user
- store_target_location
- redirect_to root_url, :alert => "You must first sign in before accessing this page."
- end
- end
-
- def admin_required
- unless admin?
- redirect_to root_url, :alert => "Not authorized to access this page."
- end
- end
+ helper_method :current_user
def redirect_to_target_or_default(default, *options)
redirect_to(session[:return_to] || default, *options)
View
74 app/controllers/comments_controller.rb
@@ -1,76 +1,48 @@
class CommentsController < ApplicationController
- before_filter :login_required, :only => [:new, :create]
- before_filter :admin_required, :only => [:edit, :update, :destroy]
- skip_before_filter :verify_authenticity_token, :only => :destroy
-
- def index
- @comments = Comment.recent.all(:limit => 30)
- respond_to do |format|
- format.html
- format.rss
- end
- end
+ load_and_authorize_resource
def new
- redirect_to root_url, :notice => "To submit a comment, please go to a specific episode first."
+ @comment = Comment.new(:parent_id => params[:parent_id], :episode_id => params[:episode_id], :user => current_user)
end
def create
@comment = current_user.comments.build(params[:comment])
@comment.request = request
- if params[:preview_button].nil? && @comment.save
- redirect_to @comment.episode, :notice => "Successfully created comment."
- else
- render 'new'
+ @comment.save
+ respond_to do |format|
+ format.html do
+ if @comment.errors.present?
+ render :new
+ else
+ redirect_to(episode_path(@comment.episode, :view => "comments"))
+ end
+ end
+ format.js
end
end
def edit
- @comment = Comment.find(params[:id])
end
def update
- @comment = Comment.find(params[:id])
- if @comment.update_attributes(params[:comment])
- redirect_to @comment.episode, :notice => "Successfully updated comment."
- else
- render 'edit'
+ @comment.update_attributes(params[:comment])
+ respond_to do |format|
+ format.html do
+ if @comment.errors.present?
+ render :edit
+ else
+ redirect_to(episode_path(@comment.episode, :view => "comments"))
+ end
+ end
+ format.js
end
end
def destroy
- @comment = Comment.find(params[:id])
@comment.destroy
- flash[:notice] = "Successfully destroyed comment."
respond_to do |format|
- format.html { redirect_to comments_path }
+ format.html { redirect_to episode_path(@comment.episode, :view => "comments") }
format.js
end
end
-
- private
-
- def check_spam(comment)
- errors = []
- if comment.spam?
- errors << "Comment rejected. Please email me the content of your comment for approval: ryan at railscasts dot com."
- elsif current_spam_question
- if params[:spam_answer] =~ /#{current_spam_question.answer}/i
- session[:spam_question_id] = nil
- else
- errors << "The given answer is incorrect, please try again."
- end
- elsif comment.spammish?
- errors << "The provided comment looks like spam. Please answer the question below."
- session[:spam_question_id] = SpamQuestion.random.id
- end
- errors << "Javascript must be enabled to submit a comment." if params[:spam_key] != APP_CONFIG['spam_key']
- errors << "Don't fill in the fake email address below, it should be left blank." unless params[:email].blank? # fake email to catch spammers
-
- unless errors.empty?
- errors << "If it still doesn't work, please let me know: ryan [at] railscasts [dot] com."
- flash.now[:alert] = errors.join(" ")
- end
- errors.empty?
- end
end
View
49 app/controllers/episodes_controller.rb
@@ -1,76 +1,57 @@
class EpisodesController < ApplicationController
- before_filter :admin_required, :only => [:new, :create, :edit, :update, :destroy]
+ load_and_authorize_resource :find_by => :param
def index
+ @tag = Tag.find(params[:tag_id]) if params[:tag_id]
if params[:search].blank?
- @episodes = Episode.published.recent
+ @episodes = (@tag ? @tag.episodes : Episode).published.recent
else
@episodes = Episode.search_published(params[:search])
end
respond_to do |format|
- format.html { @episodes = @episodes.paginate(:page => params[:page], :per_page => 10) }
+ format.html { @episodes = @episodes.paginate(:page => params[:page], :per_page => episodes_per_page) }
format.rss
end
end
- def archive
- if params[:search].blank?
- episodes = Episode.published.recent
- else
- episodes = Episode.search_published(params[:search]).sort_by(&:published_at).reverse
- end
- @episode_months = episodes.group_by(&:published_month)
- end
-
def show
- if admin?
- @episode = Episode.find_by_position!(params[:id].to_i)
- else
- @episode = Episode.published.find_by_position!(params[:id].to_i)
- end
if params[:id] != @episode.to_param
headers["Status"] = "301 Moved Permanently"
redirect_to episode_url(@episode)
else
- @comment = Comment.new(:episode => @episode)
- respond_to do |format|
- format.html
- format.rss
- end
+ @comment = Comment.new(:episode => @episode, :user => current_user)
end
end
def new
- @episode = Episode.new
- @episode.downloads.build(:format => 'mov')
- @episode.downloads.build(:format => 'm4v')
end
def create
- @episode = Episode.new(params[:episode])
if @episode.save
redirect_to @episode, :notice => "Successfully created episode."
else
- render 'new'
+ render :new
end
end
def edit
- @episode = Episode.find(params[:id])
end
def update
- @episode = Episode.find(params[:id])
if @episode.update_attributes(params[:episode])
redirect_to @episode, :notice => "Successfully updated episode."
else
- render 'edit'
+ render :edit
end
end
- def destroy
- @episode = Episode.find(params[:id])
- @episode.destroy
- redirect_to episodes_path, :notice => "Successfully destroyed episode."
+ private
+
+ def episodes_per_page
+ case params[:view]
+ when "list" then 40
+ when "grid" then 24
+ else 10
+ end
end
end
View
15 app/controllers/feedback_messages_controller.rb
@@ -0,0 +1,15 @@
+class FeedbackMessagesController < ApplicationController
+ def new
+ @feedback_message = FeedbackMessage.new
+ end
+
+ def create
+ @feedback_message = FeedbackMessage.new(params[:feedback_message])
+ if @feedback_message.save
+ Mailer.feedback(@feedback_message).deliver
+ redirect_to root_url, :notice => "Thank you for the feedback."
+ else
+ render :new
+ end
+ end
+end
View
6 app/controllers/info_controller.rb
@@ -2,12 +2,6 @@ class InfoController < ApplicationController
def about
end
- def contest
- end
-
- def feeds
- end
-
def give_back
end
end
View
39 app/controllers/spam_checks_controller.rb
@@ -1,39 +0,0 @@
-class SpamChecksController < ApplicationController
- before_filter :admin_required
-
- def index
- @spam_checks = SpamCheck.all
- end
-
- def new
- @spam_check = SpamCheck.new
- end
-
- def create
- @spam_check = SpamCheck.new(params[:spam_check])
- if @spam_check.save
- redirect_to spam_checks_url, :notice => "Successfully created spam check."
- else
- render :action => 'new'
- end
- end
-
- def edit
- @spam_check = SpamCheck.find(params[:id])
- end
-
- def update
- @spam_check = SpamCheck.find(params[:id])
- if @spam_check.update_attributes(params[:spam_check])
- redirect_to spam_checks_url, :notice => "Successfully updated spam check."
- else
- render :action => 'edit'
- end
- end
-
- def destroy
- @spam_check = SpamCheck.find(params[:id])
- @spam_check.destroy
- redirect_to spam_checks_url, :notice => "Successfully destroyed spam check."
- end
-end
View
39 app/controllers/spam_questions_controller.rb
@@ -1,39 +0,0 @@
-class SpamQuestionsController < ApplicationController
- before_filter :admin_required
-
- def index
- @spam_questions = SpamQuestion.all
- end
-
- def new
- @spam_question = SpamQuestion.new
- end
-
- def create
- @spam_question = SpamQuestion.new(params[:spam_question])
- if @spam_question.save
- redirect_to spam_questions_url, :notice => "Successfully created spam question."
- else
- render :action => 'new'
- end
- end
-
- def edit
- @spam_question = SpamQuestion.find(params[:id])
- end
-
- def update
- @spam_question = SpamQuestion.find(params[:id])
- if @spam_question.update_attributes(params[:spam_question])
- redirect_to spam_questions_url, :notice => "Successfully updated spam question."
- else
- render :action => 'edit'
- end
- end
-
- def destroy
- @spam_question = SpamQuestion.find(params[:id])
- @spam_question.destroy
- redirect_to spam_questions_url, :notice => "Successfully destroyed spam question."
- end
-end
View
34 app/controllers/spam_reports_controller.rb
@@ -1,34 +0,0 @@
-class SpamReportsController < ApplicationController
- before_filter :admin_required, :except => :create
- skip_before_filter :verify_authenticity_token, :only => :create
-
- def index
- @spam_reports = SpamReport.unconfirmed.popular
- end
-
- def show
- @spam_report = SpamReport.find(params[:id])
- end
-
- def create
- @comment = Comment.find(params[:comment_id])
- SpamReport.report_comment(@comment)
- flash[:notice] = "Thank you for reporting spam."
- respond_to do |format|
- format.html { redirect_to episode_path(@comment.episode_id) }
- format.js
- end
- end
-
- def destroy
- @spam_report = SpamReport.find(params[:id])
- @spam_report.destroy
- redirect_to spam_reports_url, :notice => "Successfully destroyed spam report."
- end
-
- def confirm
- @spam_reports = SpamReport.find_all_by_id(params[:id])
- @spam_reports.each(&:confirm!)
- redirect_to spam_reports_url, :notice => "Successfully confirmed spam report."
- end
-end
View
39 app/controllers/sponsors_controller.rb
@@ -1,39 +0,0 @@
-class SponsorsController < ApplicationController
- before_filter :admin_required
-
- def index
- @sponsors = Sponsor.find(:all)
- end
-
- def new
- @sponsor = Sponsor.new
- end
-
- def create
- @sponsor = Sponsor.new(params[:sponsor])
- if @sponsor.save
- redirect_to sponsors_url, :notice => "Successfully created sponsor."
- else
- render 'new'
- end
- end
-
- def edit
- @sponsor = Sponsor.find(params[:id])
- end
-
- def update
- @sponsor = Sponsor.find(params[:id])
- if @sponsor.update_attributes(params[:sponsor])
- redirect_to sponsors_url, :notice => "Successfully updated sponsor."
- else
- render 'edit'
- end
- end
-
- def destroy
- @sponsor = Sponsor.find(params[:id])
- @sponsor.destroy
- redirect_to sponsors_url, :notice => "Successfully destroyed sponsor."
- end
-end
View
6 app/controllers/tags_controller.rb
@@ -1,6 +0,0 @@
-class TagsController < ApplicationController
- def show
- @tag = Tag.find(params[:id])
- @episodes = @tag.episodes.published.recent.paginate(:page => params[:page], :per_page => 10, :count => { :select => "*" })
- end
-end
View
20 app/controllers/users_controller.rb
@@ -1,8 +1,8 @@
class UsersController < ApplicationController
- before_filter :login_required, :only => [:edit, :update]
+ before_filter :load_current_user, :only => [:edit, :update]
+ load_and_authorize_resource
def show
- @user = User.find(params[:id])
end
def create
@@ -14,16 +14,12 @@ def create
end
def edit
- @user = current_user
end
def update
- @user = current_user
- if @user.update_attributes(params[:user])
- redirect_to @user, :notice => "Successfully updated user."
- else
- render :action => "edit"
- end
+ @user.attributes = params[:user]
+ @user.save!
+ redirect_to @user, :notice => "Successfully updated profile."
end
def login
@@ -40,4 +36,10 @@ def logout
cookies.delete(:token)
redirect_to root_url, :notice => "You have been logged out."
end
+
+ private
+
+ def load_current_user
+ @user = current_user
+ end
end
View
55 app/helpers/application_helper.rb
@@ -1,5 +1,58 @@
+require "builder"
+
module ApplicationHelper
def textilize(text)
- Textilizer.new(text).to_html.html_safe unless text.blank?
+ CodeFormatter.new(text).to_html.html_safe unless text.blank?
+ end
+
+ def tab_link(name, url)
+ selected = url.all? { |key, value| params[key] == value }
+ link_to(name, url, :class => (selected ? "selected tab" : "tab"))
+ end
+
+ def avatar_url(comment_or_user, size = 64)
+ default_url = "#{root_url}images/guest.png"
+ token = gravatar_token(comment_or_user)
+ if token.present?
+ "http://gravatar.com/avatar/#{gravatar_token(comment_or_user)}.png?s=#{size}&d=#{CGI.escape(default_url)}"
+ else
+ default_url
+ end
+ end
+
+ # TODO refactor me into comment/user class
+ def gravatar_token(comment_or_user)
+ case comment_or_user
+ when Comment
+ token = gravatar_token(comment_or_user.user)
+ if token.present?
+ token
+ elsif comment_or_user.email.present?
+ Digest::MD5.hexdigest(comment_or_user.email.downcase)
+ end
+ when User
+ if comment_or_user.gravatar_token.present?
+ comment_or_user.gravatar_token
+ elsif comment_or_user.email.present?
+ Digest::MD5.hexdigest(comment_or_user.email.downcase)
+ end
+ else nil
+ end
+ end
+
+ def video_tag(path, options = {})
+ xml = Builder::XmlMarkup.new
+ xml.video :width => options[:width], :height => options[:height], :poster => options[:poster], :controls => "controls" do
+ xml.source :src => "#{path}.mp4?123", :type => "video/mp4"
+ # xml.source :src => "#{path}.m4v", :type => "video/mp4"
+ # xml.source :src => "#{path}.webm", :type => "video/webm"
+ # xml.source :src => "#{path}.ogv", :type => "video/ogg"
+ # xml.object :width => options[:width], :height => options[:height], :type => "application/x-shockwave-flash", :data => "/flash/flowplayer-3.2.7.swf" do
+ # xml.param :name => "movie", :value => "/flash/flowplayer-3.2.7.swf"
+ # xml.param :name => "allowfullscreen", :value => "true"
+ # xml.param :name => "bgcolor", :value => '#000000'
+ # xml.param :name => "flashvars", :value => %Q<config={"clip":{"url":"#{path}.mp4", "autoPlay":true, "autoBuffering":true}}>
+ # end
+ end.html_safe
end
end
View
14 app/helpers/comments_helper.rb
@@ -1,6 +1,10 @@
module CommentsHelper
- def format_comment(content)
- simple_format(keep_spaces_at_beginning(h(content)))
+ def format_comment(comment)
+ if comment.legacy?
+ simple_format(keep_spaces_at_beginning(h(comment.content)))
+ else
+ CodeFormatter.new(comment.content).to_html.html_safe
+ end
end
def keep_spaces_at_beginning(content)
@@ -18,4 +22,10 @@ def fix_url(url)
"http://#{url}"
end
end
+
+ def nested_comments(comments)
+ comments.map do |comment, sub_comments|
+ render(comment) + content_tag(:div, nested_comments(sub_comments), :class => "nested_comments")
+ end.join.html_safe
+ end
end
View
5 app/helpers/episodes_helper.rb
@@ -1,7 +1,2 @@
module EpisodesHelper
- def fields_for_download(download, &block)
- new_or_existing = download.new_record? ? 'new' : 'existing'
- prefix = "episode[#{new_or_existing}_download_attributes][]"
- fields_for(prefix, download, &block)
- end
end
View
2  app/helpers/feedback_messages_helper.rb
@@ -0,0 +1,2 @@
+module FeedbackMessagesHelper
+end
View
3  app/helpers/sessions_helper.rb
@@ -1,3 +0,0 @@
-module SessionsHelper
-
-end
View
2  app/helpers/spam_checks_helper.rb
@@ -1,2 +0,0 @@
-module SpamChecksHelper
-end
View
2  app/helpers/spam_questions_helper.rb
@@ -1,2 +0,0 @@
-module SpamQuestionsHelper
-end
View
2  app/helpers/spam_reports_helper.rb
@@ -1,2 +0,0 @@
-module SpamReportsHelper
-end
View
2  app/helpers/sponsors_helper.rb
@@ -1,2 +0,0 @@
-module SponsorsHelper
-end
View
3  app/helpers/tags_helper.rb
@@ -1,3 +0,0 @@
-module TagsHelper
-
-end
View
6 app/mailers/mailer.rb
@@ -0,0 +1,6 @@
+class Mailer < ActionMailer::Base
+ def feedback(message)
+ @message = message
+ mail :to => "feedback@railscasts.com", :from => @message.email, :subject => "Feedback from #{@message.name}"
+ end
+end
View
25 app/models/ability.rb
@@ -0,0 +1,25 @@
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ can :read, :episodes, ["published_at <= ?", Time.zone.now] do |episode|
+ episode.published_at <= Time.now.utc
+ end
+ can :access, :info
+ can :create, :feedback_messages
+ can [:read, :create, :login], :users
+
+ if user
+ can :logout, :users
+ can :update, :users, :id => user.id
+ can :create, :comments
+ can [:update, :destroy], :comments do |comment|
+ comment.created_at >= 15.minutes.ago
@KieranP
KieranP added a note

Should be checking that they own this comment too ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
+ if user.admin?
+ can :access, :all
+ end
+ end
+ end
+end
View
37 app/models/comment.rb
@@ -2,10 +2,11 @@ class Comment < ActiveRecord::Base
belongs_to :episode, :counter_cache => true
belongs_to :user
- validates_presence_of :episode_id
+ validates_presence_of :content, :episode_id
scope :recent, order("created_at DESC")
+ has_ancestry
acts_as_list :scope => :episode
def request=(request)
@@ -13,38 +14,4 @@ def request=(request)
self.user_agent = request.env['HTTP_USER_AGENT']
self.referrer = request.env['HTTP_REFERER']
end
-
- def matching_spam_reports
- conditions = []
- conditions << "comment_ip=#{self.class.sanitize(user_ip)}" unless user_ip.blank?
- conditions << "comment_site_url=#{self.class.sanitize(site_url)}" unless site_url.blank?
- conditions << "comment_name=#{self.class.sanitize(name)}" unless name.blank?
- SpamReport.scoped(:conditions => conditions.join(' or '))
- end
-
- def spammish?
- spam_weight > 5
- end
-
- def spam?
- spam_weight > 50
- end
-
- def spam_weight
- spam_check_weight + spam_report_weight
- end
-
- private
-
- def spam_check_weight
- SpamCheck.all.sum do |spam_check|
- spam_check.weight_for self
- end || 0
- end
-
- def spam_report_weight
- matching_spam_reports.all.sum do |spam_report|
- spam_report.hit_count
- end || 0
- end
end
View
3  app/models/download.rb
@@ -1,3 +0,0 @@
-class Download < ActiveRecord::Base
- belongs_to :episode
-end
View
118 app/models/episode.rb
@@ -2,19 +2,18 @@ class Episode < ActiveRecord::Base
has_many :comments, :dependent => :destroy
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
- has_many :downloads, :dependent => :destroy
acts_as_list
scope :published, lambda { where('published_at <= ?', Time.now.utc) }
scope :unpublished, lambda { where('published_at > ?', Time.now.utc) }
+ scope :tagged, lambda { |tag_id| tag_id ? joins(:taggings).where(:taggings => {:tag_id => tag_id}) : scoped }
scope :recent, order('position DESC')
validates_presence_of :published_at, :name
- validates_associated :downloads, :on => :update # create automatically handles validation
+ serialize :file_sizes
before_create :set_permalink
- after_update :save_downloads
# sometimes ThinkingSphinx isn't loaded for rake tasks
if respond_to? :define_index
@@ -27,12 +26,13 @@ class Episode < ActiveRecord::Base
indexes tags(:name), :as => :tag_names
has published_at
+ has taggings.tag_id, :as => :tag_ids
end
end
- def self.search_published(query)
+ def self.search_published(query, tag_id = nil)
if APP_CONFIG['thinking_sphinx']
- search(query, :conditions => { :published_at => 0..Time.now.utc.to_i },
+ search(query, :conditions => { :published_at => 0..Time.now.utc.to_i, :tag_id => tag_id },
:field_weights => { :name => 20, :description => 15, :notes => 5, :tag_names => 10 })
else
published.primitive_search(query)
@@ -42,20 +42,20 @@ def self.search_published(query)
raise e
end
- def self.primitive_search(query)
- find(:all, :conditions => primitive_search_conditions(query))
+ def self.primitive_search(query, join = "AND")
+ find(:all, :conditions => primitive_search_conditions(query, join))
end
- def published_month
- published_at.beginning_of_month
- end
-
- def mov
- downloads.find_by_format('mov')
- end
-
- def m4v
- downloads.find_by_format('m4v')
+ def similar_episodes
+ if APP_CONFIG['thinking_sphinx']
+ self.class.search(name, :conditions => { :published_at => 0..Time.now.utc.to_i }, :match_mode => :any, :page => 1, :per_page => 5,
+ :field_weights => { :name => 20, :description => 15, :notes => 5, :tag_names => 10 })
+ else
+ self.class.published.limit(5).primitive_search(name, "OR")
+ end
+ rescue ThinkingSphinx::ConnectionError => e
+ APP_CONFIG['thinking_sphinx'] = false
+ raise e
end
def tag_names=(names)
@@ -70,25 +70,20 @@ def to_param
[position, permalink].join('-')
end
- def last_published?
- self == self.class.published.last
+ def asset_name
+ [padded_position, permalink].join('-')
end
- def new_download_attributes=(download_attributes)
- download_attributes.each do |attributes|
- downloads.build(attributes)
- end
+ def asset_url(path, ext = nil)
+ "http://media.railscasts.com/assets/episodes/#{path}/#{asset_name}" + (ext ? ".#{ext}" : "")
end
- def existing_download_attributes=(download_attributes)
- downloads.reject(&:new_record?).each do |download|
- attributes = download_attributes[download.id.to_s]
- if attributes
- download.attributes = attributes
- else
- downloads.delete(download)
- end
- end
+ def padded_position
+ position.to_s.rjust(3, "0")
+ end
+
+ def last_published?
+ self == self.class.published.last
end
def duration
@@ -99,26 +94,61 @@ def duration
end
def duration=(duration)
- min, sec = *duration.split(':').map(&:to_i)
- self.seconds = min*60 + sec
+ if duration.present?
+ min, sec = *duration.split(':').map(&:to_i)
+ self.seconds = min*60 + sec
+ end
end
- private
+ def self.find_by_param!(param)
+ find_by_position!(param.to_i)
+ end
- def self.primitive_search_conditions(query)
- query.split(/\s+/).map do |word|
- '(' + %w[name description notes].map { |col| "#{col} LIKE #{sanitize('%' + word.to_s + '%')}" }.join(' OR ') + ')'
- end.join(' AND ')
+ def files
+ [
+ {:name => "source code", :info => "Project Files in Zip", :url => asset_url("sources", "zip"), :size => file_size("zip")},
+ {:name => "mp4", :info => "Full Size H.264 Video", :url => asset_url("dl/videos", "mp4"), :size => file_size("mp4")},
+ {:name => "m4v", :info => "Smaller H.264 Video", :url => asset_url("dl/videos", "m4v"), :size => file_size("m4v")},
+ {:name => "webm", :info => "Full Size VP8 Video", :url => asset_url("dl/videos", "webm"), :size => file_size("webm")},
+ {:name => "ogv", :info => "Full Size Theora Video", :url => asset_url("dl/videos", "ogv"), :size => file_size("ogv")},
+ ]
end
- def save_downloads
- if downloads.loaded?
- downloads.each do |download|
- download.save(false)
- end
+ def file_size(ext)
+ (file_sizes && file_sizes[ext]).to_i
+ end
+
+ # TODO test me
+ def available_files
+ files.select { |f| f[:size].to_i > 0 }
+ end
+
+ def load_file_sizes
+ self.file_sizes = {}
+ files.each do |file|
+ ext = file[:url][/\w+$/]
+ self.file_sizes[ext] = fetch_file_size(file[:url])
end
end
+ def fetch_file_size(path)
+ url = URI.parse(path)
+ response = Net::HTTP.start(url.host, url.port) do |http|
+ http.request_head(url.path)
+ end
+ if response.code == "200"
+ response["content-length"]
+ end
+ end
+
+ private
+
+ def self.primitive_search_conditions(query, join)
+ query.split(/\s+/).map do |word|
+ '(' + %w[name description notes].map { |col| "#{col} LIKE #{sanitize('%' + word.to_s + '%')}" }.join(' OR ') + ')'
+ end.join(" #{join} ")
+ end
+
def set_permalink
self.permalink = name.downcase.gsub(/[^0-9a-z]+/, ' ').strip.gsub(' ', '-') if name
end
View
4 app/models/feedback_message.rb
@@ -0,0 +1,4 @@
+class FeedbackMessage < ActiveRecord::Base
+ attr_accessible :name, :email, :content
+ validates_presence_of :name, :email, :content
+end
View
5 app/models/spam_check.rb
@@ -1,5 +0,0 @@
-class SpamCheck < ActiveRecord::Base
- def weight_for(comment)
- (comment.content || "").scan(/#{regexp}/i).size * weight
- end
-end
View
7 app/models/spam_question.rb
@@ -1,7 +0,0 @@
-class SpamQuestion < ActiveRecord::Base
- attr_accessible :question, :answer
-
- def self.random # there are more efficient ways to do this but it's database specific
- find(:first, :offset => rand(self.count))
- end
-end
View
44 app/models/spam_report.rb
@@ -1,44 +0,0 @@
-class SpamReport < ActiveRecord::Base
- belongs_to :comment
-
- before_create :copy_comment_attributes
-
- scope :unconfirmed, where("confirmed_at is null")
- scope :confirmed, where("confirmed_at is not null")
- scope :popular, order("hit_count desc")
-
- def self.report_comment(comment)
- if comment.matching_spam_reports.empty?
- create!(:comment => comment, :hit_count => 1)
- else
- comment.matching_spam_reports.each do |r|
- r.increment :hit_count
- r.confirmed_at = nil
- r.save!
- end
- end
- end
-
- def matching_comments
- conditions = ["0=1"]
- conditions << "user_ip=#{self.class.sanitize(comment_ip)}" unless comment_ip.blank?
- conditions << "site_url=#{self.class.sanitize(comment_site_url)}" unless comment_site_url.blank?
- conditions << "name=#{self.class.sanitize(comment_name)}" unless comment_name.blank?
- Comment.scoped(:conditions => "user_id is null and (#{conditions.join(' or ')})")
- end
-
- def confirm!
- self.update_attribute(:confirmed_at, Time.now)
- self.matching_comments.each(&:destroy)
- end
-
- private
-
- def copy_comment_attributes
- if comment
- self.comment_site_url = comment.site_url
- self.comment_ip = comment.user_ip
- self.comment_name = comment.name
- end
- end
-end
View
11 app/models/sponsor.rb
@@ -1,11 +0,0 @@
-class Sponsor < ActiveRecord::Base
- scope :active, where("active = ?", true)
-
- def position
- if force_top?
- rand
- else
- rand + 1
- end
- end
-end
View
4 app/models/tag.rb
@@ -7,4 +7,8 @@ def self.with_names(names)
Tag.find_or_create_by_name(name)
end
end
+
+ def display_name
+ name.titleize.gsub("E ", "e")
+ end
end
View
4 app/models/user.rb
@@ -23,4 +23,8 @@ def generate_token
end while self.class.exists?(:token => token)
end
end
+
+ def display_name
+ name || github_username
+ end
end
View
69 app/views/comments/_comment.html.erb
@@ -1,38 +1,37 @@
-<% @comment_counter ||= 0 %>
-<% @comment_counter += 1 %>
-<%= tag :hr if @comment_counter > 1 %>
<%= div_for comment do %>
- <% if comment.episode && !current_page?(comment.episode) %>
- <h4>
- Episode #<%= comment.episode.position %>:
- <%= link_to comment.episode.name, episode_path(:id => comment.episode, :anchor => dom_id(comment)) %>
- </h4>
- <% end %>
- <% if comment.position %>
- <span class="position"><%= comment.position %>.</span>
- <% end %>
- <span class="name">
- <% if comment.user %>
- <%= link_to comment.user.name, comment.user %>
- <% else %>
- <%= link_to_unless comment.site_url.blank?, comment.name, fix_url(comment.site_url), :rel => "nofollow" %>
- <% end %>
- </span>
- <% unless comment.created_at.nil? %>
- <span class="created_at">
- <%= comment.created_at.strftime('%b %d, %Y at %H:%M') %>
- </span>
- <% end %>
- <div class="comment_content">
- <%= format_comment(comment.content) %>
- </div>
- <div class="actions">
- <% if admin? && !comment.new_record? %>
- <%= link_to "Edit", edit_comment_path(comment) %> |
- <%= link_to "Destroy", comment, :confirm => 'Are you sure?', :method => :delete, :class => 'destroy' %> |
- <% end %>
- <% if comment.user.nil? %>
- <%= link_to "Report as Spam", spam_reports_path(:comment_id => comment), :method => :post, :class => 'spam_report' %>
- <% end %>
+ <% if comment.hidden? %>
+ <div class="hidden">
+ <div class="avatar"><%= image_tag avatar_url(comment, 24), :size => "24x24", :alt => "Avatar" %></div>
+ <div class="main">
+ <span class="name">
+ <% if comment.user %>
+ <%= link_to comment.user.display_name, comment.user %>
+ <% else %>
+ <%= link_to_unless comment.site_url.blank?, comment.name, fix_url(comment.site_url), :rel => "nofollow" %>
+ <% end %>
+ </span>
+ <span class="hidden_content">
+ <%= truncate(comment.content, :length => 90) %>
+ </span>
+ <span class="actions">
+ <%= link_to "Show", comment_path(comment, "comment[hidden]" => 0), :remote => true, :method => :put if can?(:update, comment) %>
+ </span>
+ </div>
</div>
+ <% else %>
+ <div class="avatar"><%= image_tag avatar_url(comment), :size => "64x64", :alt => "Avatar" %></div>
+ <div class="main">
+ <%= render "comments/comment_headline", :comment => comment %>
+ <div class="comment_content">
+ <%= format_comment(comment) %>
+ <div class="actions">
+ <%= link_to "Reply", new_comment_path(:parent_id => comment, :episode_id => comment.episode_id), :remote => true %>
+ <%= link_to "Edit", edit_comment_path(comment), :remote => true if can?(:update, comment) %>
+ <%= link_to "Hide", comment_path(comment, "comment[hidden]" => 1), :remote => true, :method => :put if can?(:update, comment) %>
+ <%= link_to "Destroy", comment, :confirm => "Are you sure you want to delete this comment?", :method => :delete, :remote => true if can?(:destroy, comment) %>
+ </div>
+ </div>
+ </div>
+ <% end %>
+ <div class="clear"></div>
<% end %>
View
17 app/views/comments/_comment_headline.html.erb
@@ -0,0 +1,17 @@
+<div class="headline">
+ <% if comment.new_record? %>
+ <span class="position">New comment as</span>
+ <% end %>
+ <span class="name">
+ <% if comment.user %>
+ <%= link_to comment.user.display_name, comment.user %>
+ <% else %>
+ <%= link_to_unless comment.site_url.blank?, comment.name, fix_url(comment.site_url), :rel => "nofollow" %>
+ <% end %>
+ </span>
+ <% unless comment.created_at.nil? %>
+ <span class="created_at">
+ <%= comment.created_at.strftime('%b %d, %Y at %H:%M') %>
+ </span>
+ <% end %>
+</div>
View
84 app/views/comments/_form.html.erb
@@ -1,16 +1,70 @@
-<%= form_for @comment do |f| %>
- <%= f.error_messages %>
- <div><%= f.hidden_field :episode_id %></div>
- <p>
- <%= f.label :content, 'Comment', :class => 'required' %>
- <span class="info">
- (use <a href="http://pastie.org" target="_blank">pastie</a>
- or <a href="http://gist.github.com" target="_blank">gist</a> for code)
- </span><br />
- <%= f.text_area :content, :rows => '12', :cols => 35 %>
- </p>
- <p>
- <%= f.submit "Preview", :name => 'preview_button' %>
- <%= f.submit "Submit" %>
- </p>
+<% if current_user.nil? %>
+ <p>First <%= link_to "sign in through GitHub", login_path(:return_to => request.url) %> to post a comment.</p>
+<% else %>
+ <%= div_for @comment do %>
+ <div class="avatar"><%= image_tag avatar_url(@comment), :size => "64x64", :alt => "Avatar" %></div>
+ <div class="main">
+ <%= render "comments/comment_headline", :comment => @comment %>
+ <div class="comment_content">
+ <%= form_for @comment, :remote => true do |f| %>
+ <%= f.error_messages %>
+ <div>
+ <%= f.hidden_field :episode_id %>
+ <%= f.hidden_field :parent_id %>
+ </div>
+ <div class="formatting">
+ Use Markdown for formatting. <%= link_to "See examples.", "javascript:void(0)", :class => "markdown_link" %>
+ <div class="markdown_examples" style="display: none">
+ <table>
+ <tr>
+ <td><pre>[Link](http://example.com/)</pre></td>
+ <td><a href="http://example.com/">Link</a></td>
+ </tr>
+ <tr>
+ <td><pre>*Italic*</pre></td>
+ <td><em>Italic</em></td>
+ </tr>
+ <tr>
+ <td><pre>**Bold**</pre></td>
+ <td><strong>Bold</strong></td>
+ </tr>
+ <tr>
+ <td><pre>
+* Listed
+* Items
+ </pre></td>
+ <td><ul><li>Listed</li><li>Items</li></ul></td>
+ </tr>
+ <tr>
+ <td><pre>&gt; Block quote</pre></td>
+ <td><blockquote><p>Block quote</p></blockquote></td>
+ </tr>
+ <tr>
+ <td><pre>`Inline code`</pre></td>
+ <td><code>Inline code</code></td>
+ </tr>
+ <tr>
+ <td>
+ <pre>
+``` ruby
+puts "code block"
+```
+ </pre>
+ </td>
+ <td><%= raw CodeFormatter.new("``` ruby\nputs \"code block\"\n```").to_html %></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="field">
+ <%= f.text_area :content, :rows => '12', :cols => 65 %>
+ </div>
+ <div class="actions">
+ <%= f.submit(@comment.new_record? ? "Post Comment" : "Update Comment") %>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ <div class="clear"></div>
+ <% end %>
<% end %>
View
10 app/views/comments/create.js.erb
@@ -0,0 +1,10 @@
+<% if @comment.parent_id.present? %>
+ var container = $("#<%= dom_id(@comment.parent) %>").next(".nested_comments");
+<% else %>
+ var container = $("#comments");
+<% end %>
+<% if @comment.errors.present? %>
+ container.find("form").prepend("<%= escape_javascript error_messages_for(@comment) %>");
+<% else %>
+ container.children("#new_comment").replaceWith("<%= escape_javascript render(@comment) %>");
+<% end %>
View
2  app/views/comments/destroy.js.erb
@@ -1 +1 @@
-$("#<%= dom_id(@comment) %>").html("<%= escape_javascript(flash.delete(:notice)) %>");
+$("#<%= dom_id(@comment) %>").remove();
View
2  app/views/comments/edit.html.erb
@@ -1,5 +1,5 @@
<% title "Edit Comment" %>
<div class="content">
- <%= render :partial => 'form' %>
+ <%= render "form" %>
</div>
View
2  app/views/comments/edit.js.erb
@@ -0,0 +1,2 @@
+$("#<%= dom_id(@comment) %>").replaceWith("<%= escape_javascript render("form") %>");
+$("#<%= dom_id(@comment) %>").find("#comment_content")[0].focus();
View
8 app/views/comments/index.html.erb
@@ -1,8 +0,0 @@
-<% title "Recent Comments" %>
-<% content_for :head do %>
- <link rel="alternate" type="application/rss+xml" title="Comments RSS" href="http://feeds.feedburner.com/railscasts_comments" />
-<% end %>
-
-<div id="comments" class="content">
- <%= render @comments %>
-</div>
View
19 app/views/comments/index.rss.builder
@@ -1,19 +0,0 @@
-xml.instruct! :xml, :version => "1.0"
-xml.rss :version => "2.0" do
- xml.channel do
- xml.title "Railscasts Comments"
- xml.description "Recent comments for all Railscasts episodes."
- xml.link comments_url(:format => 'rss')
-
- @comments.each do |comment|
- xml.item do
- xml.title "Comment for Episode #{comment.episode.position}: #{comment.episode.name}"
- xml.description comment.content
- xml.author comment.name
- xml.pubDate comment.created_at.to_s(:rfc822)
- xml.link episode_url(:id => comment.episode, :anchor => dom_id(comment))
- xml.guid({:isPermaLink => "false"}, comment_url(comment))
- end
- end
- end
-end
View
14 app/views/comments/new.html.erb
@@ -1,15 +1,5 @@
-<% if params[:preview_button] %>
- <% title "Comment Preview" %>
-<% else %>
- <% title "New Comment" %>
-<% end %>
+<% title "New Comment" %>
<div class="content">
- <% if params[:preview_button] %>
- <div id="comments">
- <%= render @comment %>
- </div>
- <% end %>
-
- <%= render :partial => 'form' %>
+ <%= render "form" %>
</div>
View
2  app/views/comments/new.js.erb
@@ -0,0 +1,2 @@
+$("#comment_<%= params[:parent_id] %>").next(".nested_comments").prepend("<%= escape_javascript render("form") %>");
+$("#comment_<%= params[:parent_id] %>").next(".nested_comments").find("#comment_content")[0].focus();
View
5 app/views/comments/update.js.erb
@@ -0,0 +1,5 @@
+<% if @comment.errors.present? %>
+ $("#<%= dom_id(@comment) %>").find("form").prepend("<%= escape_javascript error_messages_for(@comment) %>");
+<% else %>
+ $("#<%= dom_id(@comment) %>").replaceWith("<%= escape_javascript render(@comment) %>");
+<% end %>
View
4 app/views/episodes/_comments.html.erb
@@ -0,0 +1,4 @@
+<div id="comments">
+ <%= nested_comments @episode.comments.includes(:user).arrange(:order => "created_at asc") %>
+ <%= render "comments/form" %>
+</div>
View
51 app/views/episodes/_episode.html.erb
@@ -1,47 +1,16 @@
<div class="episode">
- <div class="side">
- <div class="number">#<%= episode.position %></div>
- <div class="published_at"><%= episode.published_at.strftime('%b %d, %Y') %></div>
- <div class="comments_count"><%= link_to pluralize(episode.comments.size, 'comment'), episode_path(:id => episode, :anchor => 'comments') %></div>
- <% if admin? %>
- <div class="actions">
- <p><%= link_to 'Edit', edit_episode_path(episode) %></p>
- <p><%= link_to 'Destroy', episode, :method => :delete, :confirm => 'Are you sure?' %></p>
- </div>
- <% end %>
- </div>
+ <div class="screenshot"><%= link_to image_tag("/assets/episodes/stills/#{episode.asset_name}.png", :size => "200x125", :alt => episode.name), episode %></div>
<div class="main">
- <h2><%= link_to_unless_current episode.name, episode %></h2>
+ <div class="info">
+ <span class="number">Episode #<%= episode.position %></span> &ndash;
+ <span class="published_at"><%= episode.published_at.strftime('%b %d, %Y') %></span>
+ </div>
+ <h2><%= link_to episode.name, episode %></h2>
<div class="description"><%= episode.description %></div>
- <% unless episode.tags.empty? %>
- <div class="tags">
- Tags:
- <% for tag in episode.tags %>
- <%= link_to tag.name, tag %>
- <% end %>
- </div>
- <% end %>
- <div class="download">
- <% if episode.mov %>
- <%= link_to "Download", episode.mov.url %>
- <span class="stats">
- (<%= number_to_human_size(episode.mov.bytes) %>, <%= episode.duration %>)
- </span>
- <% end %>
- <% if episode.m4v %>
- <div class="alt_download">
- <%= link_to "alternative download", episode.m4v.url %> for iPod &amp; Apple TV
- <span class="stats">
- (<%= number_to_human_size(episode.m4v.bytes) %>, <%= episode.duration %>)
- </span>
- </div>
- <% end %>
+ <div class="watch">
+ <%= link_to "Watch Episode", episode, :class => "watch_button" %>
+ <span class="duration">(<%= pluralize (episode.seconds/60).round, "minute" %>)</span>
</div>
- <% if episode.asciicasts? %>
- <div class="asciicasts">
- <%= link_to "Read it on ASCIIcasts", "http://asciicasts.com/episodes/#{episode.to_param}" %>
- </div>
- <% end %>
- <%= yield if block_given? %>
</div>
+ <div class="clear"></div>
</div>
View
59 app/views/episodes/_form.html.erb
@@ -1,49 +1,34 @@
<div class="content">
<%= form_for @episode do |f| %>
<%= f.error_messages %>
- <p>
- <%= f.label :name %><br />
+ <div class="field">
+ <%= f.label :name %>
<%= f.text_field :name %>
- </p>
- <p>
- <%= f.label :tag_names, 'Tags' %><br />
+ </div>
+ <div class="field">
+ <%= f.label :tag_names, "Tags" %>
<%= f.text_field :tag_names %>
- </p>
- <p>
- <%= f.label :description %><br />
+ </div>
+ <div class="field">
+ <%= f.label :description %>
<%= f.text_area :description, :rows => 6 %>
- </p>
- <p>
- <%= f.label :notes %><br />
+ </div>
+ <div class="field">
+ <%= f.label :notes %>
<%= f.text_area :notes, :rows => 10 %>
- </p>
- <p>
- <%= f.label :published_at, 'Publish Date' %><br />
+ </div>
+ <div class="field">
+ <%= f.label :published_at, "Publish Date" %>
<%= f.date_select :published_at %>
- </p>
- <p>
- <%= f.label :duration, 'Duration' %><br />
+ </div>
+ <div class="field">
+ <%= f.label :duration %>
<%= f.text_field :duration %>
- </p>