Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit, extracted from working Teldra project and sanitized for…

… your protection
  • Loading branch information...
commit ad846852d344dbf3f64da28257be1f0f5172ef1b 0 parents
@joshsusser authored
Showing with 16,076 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +3 −0  README
  3. +10 −0 Rakefile
  4. +67 −0 app/controllers/admin/articles_controller.rb
  5. +19 −0 app/controllers/admin/comments_controller.rb
  6. +7 −0 app/controllers/admin/panel_controller.rb
  7. +10 −0 app/controllers/application.rb
  8. +87 −0 app/controllers/blog_controller.rb
  9. +30 −0 app/controllers/sessions_controller.rb
  10. +11 −0 app/controllers/stylesheets_controller.rb
  11. +2 −0  app/helpers/admin/articles_helper.rb
  12. +2 −0  app/helpers/admin/panel_helper.rb
  13. +52 −0 app/helpers/application_helper.rb
  14. +43 −0 app/helpers/blog_helper.rb
  15. +2 −0  app/helpers/sessions_helper.rb
  16. +2 −0  app/helpers/stylesheets_helper.rb
  17. +143 −0 app/models/article.rb
  18. +26 −0 app/models/article_sweeper.rb
  19. +46 −0 app/models/comment.rb
  20. +3 −0  app/models/reject.rb
  21. +52 −0 app/models/tag.rb
  22. +42 −0 app/models/tagging.rb
  23. +86 −0 app/models/user.rb
  24. +16 −0 app/views/admin/articles/_form.html.erb
  25. +11 −0 app/views/admin/articles/edit.html.erb
  26. +32 −0 app/views/admin/articles/index.html.erb
  27. +10 −0 app/views/admin/articles/new.html.erb
  28. +32 −0 app/views/admin/articles/show.html.erb
  29. +15 −0 app/views/admin/comments/_comment.html.erb
  30. +6 −0 app/views/admin/comments/index.html.erb
  31. +12 −0 app/views/admin/panel/index.html.erb
  32. +12 −0 app/views/blog/_article.atom.builder
  33. +80 −0 app/views/blog/_article.html.erb
  34. +6 −0 app/views/blog/_article_list.html.erb
  35. +10 −0 app/views/blog/_comment.html.erb
  36. +5 −0 app/views/blog/index.atom.builder
  37. +6 −0 app/views/blog/index.html.erb
  38. +1 −0  app/views/blog/results.html.erb
  39. +3 −0  app/views/blog/show.html.erb
  40. +5 −0 app/views/blog/tag.atom.builder
  41. +10 −0 app/views/blog/tag.html.erb
  42. +5 −0 app/views/blog/tags.html.erb
  43. +21 −0 app/views/layouts/admin.html.erb
  44. +58 −0 app/views/layouts/blog.html.erb
  45. +12 −0 app/views/sessions/new.html.erb
  46. +573 −0 app/views/stylesheets/blog.ncss
  47. +108 −0 config/boot.rb
  48. +23 −0 config/database.yml.example
  49. +59 −0 config/environment.rb
  50. +18 −0 config/environments/development.rb
  51. +18 −0 config/environments/development_cached.rb
  52. +19 −0 config/environments/production.rb
  53. +22 −0 config/environments/test.rb
  54. +22 −0 config/geminstaller.yml
  55. +2 −0  config/initializers/dependencies.rb
  56. +10 −0 config/initializers/inflections.rb
  57. +5 −0 config/initializers/mime_types.rb
  58. +1 −0  config/initializers/site_settings.rb
  59. +37 −0 config/routes.rb
  60. +97 −0 db/migrate/001_initial_schema.rb
  61. +98 −0 db/schema.rb
  62. +2 −0  doc/README_FOR_APP
  63. +128 −0 lib/authenticated_system.rb
  64. +10 −0 lib/authenticated_test_helper.rb
  65. +25 −0 lib/tasks/teldra.rake
  66. +1 −0  log/.gitemptydir
  67. +40 −0 public/.htaccess
  68. +30 −0 public/404.html
  69. +30 −0 public/422.html
  70. +30 −0 public/500.html
  71. +10 −0 public/dispatch.cgi
  72. +24 −0 public/dispatch.fcgi
  73. +10 −0 public/dispatch.rb
  74. 0  public/favicon.ico
  75. BIN  public/images/background.gif
  76. BIN  public/images/delete.png
  77. BIN  public/images/eye.png
  78. BIN  public/images/feed16x16.png
  79. BIN  public/images/gravatar.gif
  80. BIN  public/images/header_shadow.gif
  81. BIN  public/images/rails.png
  82. BIN  public/images/spinner.gif
  83. +2 −0  public/javascripts/application.js
  84. +963 −0 public/javascripts/controls.js
  85. +972 −0 public/javascripts/dragdrop.js
  86. +1,120 −0 public/javascripts/effects.js
  87. +4,225 −0 public/javascripts/prototype.js
  88. +65 −0 public/javascripts/showdown-gui.js
  89. +421 −0 public/javascripts/showdown.js
  90. +140 −0 public/stylesheets/admin.css
  91. +609 −0 public/stylesheets/blog.css
  92. +3 −0  script/about
  93. +3 −0  script/console
  94. +3 −0  script/destroy
  95. +3 −0  script/generate
  96. +3 −0  script/performance/benchmarker
  97. +3 −0  script/performance/profiler
  98. +3 −0  script/performance/request
  99. +3 −0  script/plugin
  100. +3 −0  script/process/inspector
  101. +3 −0  script/process/reaper
  102. +3 −0  script/process/spawner
  103. +3 −0  script/runner
  104. +3 −0  script/server
  105. +102 −0 script/spec_server
  106. +20 −0 test/fixtures/basic/articles.yml
  107. +74 −0 test/fixtures/blog/articles.yml
  108. +32 −0 test/fixtures/blog/comments.yml
  109. +16 −0 test/fixtures/blog/taggings.yml
  110. +8 −0 test/fixtures/blog/tags.yml
  111. +40 −0 test/fixtures/tags/articles.yml
  112. +20 −0 test/fixtures/tags/taggings.yml
  113. +16 −0 test/fixtures/tags/tags.yml
  114. +15 −0 test/fixtures/user_authentication/users.yml
  115. +9 −0 test/fixtures/users.yml
  116. +79 −0 test/functional/admin/articles_controller_test.rb
  117. +38 −0 test/functional/admin/comments_controller_test.rb
  118. +8 −0 test/functional/admin/panel_controller_test.rb
  119. +188 −0 test/functional/blog_controller_test.rb
  120. +75 −0 test/functional/sessions_controller_test.rb
  121. +8 −0 test/functional/stylesheets_controller_test.rb
  122. +11 −0 test/more_helpers.rb
  123. +47 −0 test/test_helper.rb
  124. +218 −0 test/unit/article_test.rb
  125. +109 −0 test/unit/authenticated_user_test.rb
  126. +33 −0 test/unit/comment_test.rb
  127. +8 −0 test/unit/reject_test.rb
  128. +40 −0 test/unit/tag_test.rb
  129. +1 −0  test/unit/tagging_test.rb
  130. +9 −0 test/unit/user_test.rb
  131. +2 −0  vendor/plugins/fixture_scenarios/History.txt
  132. +20 −0 vendor/plugins/fixture_scenarios/MIT-LICENSE
  133. +123 −0 vendor/plugins/fixture_scenarios/README
  134. +22 −0 vendor/plugins/fixture_scenarios/Rakefile
  135. +5 −0 vendor/plugins/fixture_scenarios/init.rb
  136. +1 −0  vendor/plugins/fixture_scenarios/install.rb
  137. +268 −0 vendor/plugins/fixture_scenarios/lib/fixture_scenarios.rb
  138. +86 −0 vendor/plugins/fixture_scenarios/tasks/fixture_scenarios_tasks.rake
  139. +8 −0 vendor/plugins/fixture_scenarios/test/fixture_scenarios_test.rb
  140. +1 −0  vendor/plugins/fixture_scenarios/uninstall.rb
  141. +20 −0 vendor/plugins/simply_versioned/CHANGES
  142. +20 −0 vendor/plugins/simply_versioned/MIT-LICENSE
  143. +93 −0 vendor/plugins/simply_versioned/README
  144. +31 −0 vendor/plugins/simply_versioned/Rakefile
  145. +11 −0 vendor/plugins/simply_versioned/generators/simply_versioned_migration/simply_versioned_migration_generator.rb
  146. +18 −0 vendor/plugins/simply_versioned/generators/simply_versioned_migration/templates/migration.rb
  147. +1 −0  vendor/plugins/simply_versioned/init.rb
  148. +1 −0  vendor/plugins/simply_versioned/install.rb
  149. +162 −0 vendor/plugins/simply_versioned/lib/simply_versioned.rb
  150. +47 −0 vendor/plugins/simply_versioned/lib/version.rb
  151. +122 −0 vendor/plugins/simply_versioned/rdoc/classes/SoftwareHeretics.html
  152. +111 −0 vendor/plugins/simply_versioned/rdoc/classes/SoftwareHeretics/ActiveRecord.html
  153. +149 −0 vendor/plugins/simply_versioned/rdoc/classes/SoftwareHeretics/ActiveRecord/SimplyVersioned.html
  154. +159 −0 vendor/plugins/simply_versioned/rdoc/classes/SoftwareHeretics/ActiveRecord/SimplyVersioned/ClassMethods.html
  155. +178 −0 ...or/plugins/simply_versioned/rdoc/classes/SoftwareHeretics/ActiveRecord/SimplyVersioned/InstanceMethods.html
  156. +286 −0 ...ugins/simply_versioned/rdoc/classes/SoftwareHeretics/ActiveRecord/SimplyVersioned/VersionsProxyMethods.html
  157. +1 −0  vendor/plugins/simply_versioned/rdoc/created.rid
  158. +216 −0 vendor/plugins/simply_versioned/rdoc/files/README.html
  159. +112 −0 vendor/plugins/simply_versioned/rdoc/files/lib/simply_versioned_rb.html
  160. +112 −0 vendor/plugins/simply_versioned/rdoc/files/lib/version_rb.html
  161. +32 −0 vendor/plugins/simply_versioned/rdoc/fr_class_index.html
  162. +29 −0 vendor/plugins/simply_versioned/rdoc/fr_file_index.html
  163. +36 −0 vendor/plugins/simply_versioned/rdoc/fr_method_index.html
  164. +24 −0 vendor/plugins/simply_versioned/rdoc/index.html
  165. +208 −0 vendor/plugins/simply_versioned/rdoc/rdoc-style.css
  166. +4 −0 vendor/plugins/simply_versioned/tasks/simply_versioned_tasks.rake
  167. +214 −0 vendor/plugins/simply_versioned/test/simply_versioned_test.rb
  168. +56 −0 vendor/plugins/simply_versioned/test/test_helper.rb
  169. +1 −0  vendor/plugins/simply_versioned/uninstall.rb
  170. +20 −0 vendor/plugins/teldra_importer/MIT-LICENSE
  171. +13 −0 vendor/plugins/teldra_importer/README
  172. +22 −0 vendor/plugins/teldra_importer/Rakefile
  173. +9 −0 vendor/plugins/teldra_importer/init.rb
  174. +11 −0 vendor/plugins/teldra_importer/lib/mephisto/article.rb
  175. +7 −0 vendor/plugins/teldra_importer/lib/mephisto/assigned_section.rb
  176. +6 −0 vendor/plugins/teldra_importer/lib/mephisto/comment.rb
  177. +6 −0 vendor/plugins/teldra_importer/lib/mephisto/content.rb
  178. +53 −0 vendor/plugins/teldra_importer/lib/mephisto/importer.rb
  179. +5 −0 vendor/plugins/teldra_importer/lib/mephisto/section.rb
  180. +6 −0 vendor/plugins/teldra_importer/lib/mephisto/tag.rb
  181. +7 −0 vendor/plugins/teldra_importer/lib/mephisto/tagging.rb
  182. +5 −0 vendor/plugins/teldra_importer/lib/mephisto/user.rb
  183. 0  vendor/plugins/teldra_importer/lib/teldra_importer.rb
  184. +4 −0 vendor/plugins/teldra_importer/tasks/teldra_importer_tasks.rake
  185. +8 −0 vendor/plugins/teldra_importer/test/teldra_importer_test.rb
  186. +120 −0 vendor/plugins/test_spec_on_rails/README
  187. +24 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails.rb
  188. +21 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/dummy_response.rb
  189. +33 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/should_redirect.rb
  190. +42 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/should_select.rb
  191. +16 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/should_validate.rb
  192. +38 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_dummy.rb
  193. +29 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_spec_ext.rb
  194. +15 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_status.rb
  195. +14 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_template.rb
  196. +29 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_unit_ext.rb
  197. +25 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/test_url.rb
  198. +37 −0 vendor/plugins/test_spec_on_rails/lib/test/spec/rails/use_controller.rb
  199. +20 −0 vendor/plugins/validates_existence/MIT-LICENSE
  200. +22 −0 vendor/plugins/validates_existence/README
  201. +22 −0 vendor/plugins/validates_existence/Rakefile
  202. +1 −0  vendor/plugins/validates_existence/init.rb
  203. +47 −0 vendor/plugins/validates_existence/lib/validates_existence.rb
4 .gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+config/database.yml
+routes.txt
+log/*.log
3  README
@@ -0,0 +1,3 @@
+# Teldra #
+
+a blogging system by Josh Susser
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
67 app/controllers/admin/articles_controller.rb
@@ -0,0 +1,67 @@
+class Admin::ArticlesController < ApplicationController
+ layout "admin"
+
+ before_filter :login_required
+
+ cache_sweeper :article_sweeper, :only => [:create, :update, :destroy]
+
+ # GET /admin/articles
+ def index
+ @articles = Article.find(:all, :order => "created_at DESC")
+ end
+
+ # GET /admin/articles/:id
+ def show
+ @article = Article.find(params[:id])
+ @comments = @article.comments
+ end
+
+ # GET /admin/articles/new
+ def new
+ @article = Article.new
+ end
+
+ # GET /admin/articles/:id/edit
+ def edit
+ @article = Article.find(params[:id])
+ end
+
+ # POST /admin/articles
+ def create
+ @article = Article.new(params[:article])
+ @article.user_id = session[:user_id]
+
+ @article.published_at = Time.now
+
+ if @article.save
+ flash[:success] = 'Article was successfully created.'
+ redirect_to([:admin, @article])
+ else
+ # flash[:warning] = 'Article could not be created.'
+ render :action => "new"
+ end
+ end
+
+ # PUT /admin/articles/:id
+ def update
+ @article = Article.find(params[:id])
+
+ save_new_version = !(params[:commit].to_s !~ /save new version/i)
+ if @article.with_versioning(save_new_version) { |a| a.update_attributes(params[:article]) }
+ flash[:success] = 'Article was successfully updated.'
+ redirect_to edit_admin_article_url(@article)
+ else
+ # flash[:warning] = 'Article could not be updated.'
+ render :action => "edit"
+ end
+ end
+
+ # DELETE /admin/articles/:id
+ def destroy
+ @article = Article.find(params[:id])
+ @article.destroy
+
+ flash[:success] = 'Article was deleted.'
+ redirect_to(admin_articles_url)
+ end
+end
19 app/controllers/admin/comments_controller.rb
@@ -0,0 +1,19 @@
+class Admin::CommentsController < ApplicationController
+ layout "admin"
+
+ before_filter :login_required
+
+ cache_sweeper :article_sweeper, :only => [:destroy]
+
+ # GET /admin/comments
+ def index
+ @comments = Comment.paginate(:all, :page => params[:page], :order => "created_at DESC")
+ end
+
+ # DELETE /admin/comments/:id
+ def destroy
+ @comment = Comment.find(params[:id])
+ @comment.destroy
+ redirect_to request.referer || admin_comments_url # nil in tests
+ end
+end
7 app/controllers/admin/panel_controller.rb
@@ -0,0 +1,7 @@
+class Admin::PanelController < ApplicationController
+ layout "admin"
+ before_filter :login_required
+
+ def index
+ end
+end
10 app/controllers/application.rb
@@ -0,0 +1,10 @@
+
+class ApplicationController < ActionController::Base
+ include AuthenticatedSystem
+
+ helper :all # include all helpers, all the time
+
+ # See ActionController::RequestForgeryProtection for details
+ # Uncomment the :secret if you're not using the cookie session store
+ protect_from_forgery # :secret => '436456a364e065c4bd3cb0ac2622be70'
+end
87 app/controllers/blog_controller.rb
@@ -0,0 +1,87 @@
+class BlogController < ApplicationController
+
+ caches_page :index, :tags, :tag, :show, :page
+ cache_sweeper :article_sweeper, :only => [:create_comment]
+
+ before_filter :setup_sidebar, :except => [:create_comment]
+
+ # main blog page - show recent posts (short form)
+ def index
+ respond_to do |format|
+ format.html do
+ @articles = Article.posts.recent.limit(5)
+ @archives = Article.posts.recent
+ end
+ format.atom { @articles = Article.posts.recent.limit(10) }
+ end
+ end
+
+ # list all tags
+ def tags
+ @tags = Tag.find_all_popular
+ end
+
+ # show posts for :tag (result list form)
+ def tag
+ if @tag = Tag.find_by_name(params[:tag])
+ @articles = Article.find_all_by_tag_list([@tag])
+ respond_to do |format|
+ format.html
+ format.atom
+ end
+ else
+ head :not_found
+ end
+ end
+
+ def search
+ @articles = Article.search(params[:q])
+ render :action => 'results'
+ end
+
+ # show post (extended form)
+ def show
+ @article = Article.find_post_by_date_and_slug(params[:year], params[:month], params[:day], params[:slug])
+ @page_title = @article.title
+ end
+
+ # show page
+ def page
+ @article = Article.find_page_by_slug(params[:slug])
+ @page_title = @article.title
+ render :action => 'show'
+ end
+
+ def create_comment
+ @article = Article.find(params[:article_id])
+ params[:comment].merge!(:author_ip => request.remote_ip, :user_id => session[:user_id])
+ if check_comment_spam(params[:comment])
+ @comment = @article.comments.build(params[:comment])
+ if @comment.save
+ redirect_to post_url(*@article.post_path_params)
+ else
+ flash[:warning] = "COMMENT ERROR" # FIXME comment missing something
+ redirect_to post_url(*@article.post_path_params)
+ end
+ else
+ Reject.create(params[:comment].merge(:article_id => @article.id))
+ redirect_to post_url(*@article.post_path_params)
+ end
+ end
+
+ private
+
+ def setup_sidebar
+ unless request.format.to_sym == :atom
+ @tag_cloud = Tag.find_all_popular
+ end
+ end
+
+ def check_comment_spam(data)
+ if data[:body].blank?
+ data[:body] = data.delete(:tofu)
+ else
+ false
+ end
+ end
+end
30 app/controllers/sessions_controller.rb
@@ -0,0 +1,30 @@
+# This controller handles the login/logout function of the site.
+class SessionsController < ApplicationController
+ layout false
+
+ # render new.rhtml
+ def new
+ end
+
+ def create
+ self.current_user = User.authenticate(params[:login], params[:password])
+ if logged_in?
+ if params[:remember_me] == "1"
+ self.current_user.remember_me
+ cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
+ end
+ redirect_back_or_default(panel_url)
+ flash[:success] = "You have been logged in successfully"
+ else
+ render :action => 'new'
+ end
+ end
+
+ def destroy
+ self.current_user.forget_me if logged_in?
+ cookies.delete :auth_token
+ reset_session
+ flash[:success] = "You have been logged out."
+ redirect_to root_url
+ end
+end
11 app/controllers/stylesheets_controller.rb
@@ -0,0 +1,11 @@
+class StylesheetsController < ApplicationController
+ before_filter :set_headers
+ after_filter { |c| c.cache_page }
+ session :off
+ layout nil
+
+ private
+ def set_headers
+ headers['Content-Type'] = 'text/css; charset=utf-8'
+ end
+end
2  app/helpers/admin/articles_helper.rb
@@ -0,0 +1,2 @@
+module Admin::ArticlesHelper
+end
2  app/helpers/admin/panel_helper.rb
@@ -0,0 +1,2 @@
+module Admin::PanelHelper
+end
52 app/helpers/application_helper.rb
@@ -0,0 +1,52 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+ def if_blank(str, alternate)
+ str.blank? ? alternate : str
+ end
+
+ # flash_div
+ # use to display specified flash messages
+ # defaults to standard set: [:success, :message, :warning]
+ # example:
+ # <%= flash_div %>
+ # example with other keys:
+ # <%= flash_div :notice, :violation %>
+ # renders like:
+ # <div class="flash flash-success">Positive - successful action</div>
+ # <div class="flash flash-message">Neutral - reminders, status</div>
+ # <div class="flash flash-warning">Negative - error, unsuccessful action</div>
+ def flash_div(*keys)
+ keys = [:success, :message, :warning] if keys.empty?
+ keys.compact.collect do |key|
+ flash[key].blank? ? nil : content_tag(:div, flash[key], :class => "flash flash-#{key}")
+ end.compact.join("\n")
+ end
+
+ def current_user_name
+ session[:user_id] ? User.find(session[:user_id]).name : "beautiful stranger"
+ end
+
+ def comment_period_options
+ [['Never expire', -1],
+ ['Are not allowed', 0],
+ ['Expire 24 hours after publishing', 1],
+ ['Expire 1 week after publishing', 7],
+ ['Expire 1 month after publishing', 30],
+ ['Expire 3 months after publishing', 90]]
+ end
+
+ # defeat spam links by adding a rel="nofollow" attribute to <a> tags in submitted comments
+ def nofollowize
+ tokenizer = HTML::Tokenizer.new(text)
+ out = ""
+ while token = tokenizer.next
+ node = HTML::Node.parse(nil, 0, 0, token, false)
+ if node.tag? && node.name.downcase == "a"
+ node.attributes["rel"] = "nofollow" unless node.attributes.nil?
+ end
+ out << node.to_s
+ end
+ out
+ end
+
+end
43 app/helpers/blog_helper.rb
@@ -0,0 +1,43 @@
+module BlogHelper
+
+ def doc_title
+ if @page_title
+ h("#{SITE_TITLE} - #{@page_title}")
+ else
+ h(SITE_TITLE)
+ end
+ end
+
+ def link_to_article(article)
+ link_to h(article.title), article.post? ? post_path(*article.post_path_params) : page_path(article.slug)
+ end
+
+ def links_to_tags(tags)
+ if tags.blank?
+ "[none]"
+ else
+ tags.collect { |tag| link_to h(tag.name), tag_path(tag) }.join(", ")
+ end
+ end
+
+ def tag_cloud
+ @tag_cloud.collect { |tag| link_to(h(tag.name), tag_path(tag)) }.join(" ")
+ # content_tag(:ul,
+ # @tag_cloud.collect { |tag| content_tag(:li, link_to(h(tag.name), tag_path(tag))) }.join("\n"),
+ # :id => "tags")
+ end
+
+ def archive_list
+ current_date = nil
+ list = []
+ @archives.each do |post|
+ date = Date.new(post.published_at.year, post.published_at.month, 1)
+ if date != current_date
+ current_date = date
+ list << content_tag(:li, current_date.strftime("%B %Y"), :class => "month")
+ end
+ list << content_tag(:li, link_to(h(post.title), post_path(*post.post_path_params)), :class => "post")
+ end
+ list.join("\n")
+ end
+end
2  app/helpers/sessions_helper.rb
@@ -0,0 +1,2 @@
+module SessionsHelper
+end
2  app/helpers/stylesheets_helper.rb
@@ -0,0 +1,2 @@
+module StylesheetsHelper
+end
143 app/models/article.rb
@@ -0,0 +1,143 @@
+class Article < ActiveRecord::Base
+
+ class CommentNotAllowed < StandardError; end
+
+ # kinds of articles
+ POST = 0
+ PAGE = 1
+
+ after_create :set_tag_list_after_create
+
+ simply_versioned
+
+ belongs_to :user
+ has_many :comments, :order => "comments.created_at"
+
+ has_many :taggings, :dependent => :destroy
+ has_many :tags, :through => :taggings, :order => "name"
+
+ validates_presence_of :user_id
+ validates_presence_of :slug
+ validates_presence_of :title
+ validates_presence_of :body
+
+ has_finder :posts, :conditions => "kind = #{POST}"
+ has_finder :pages, :conditions => "kind = #{PAGE}"
+ has_finder :published, :conditions => "published_at IS NOT NULL"
+ has_finder :recent, :conditions => "published_at IS NOT NULL", :order => "published_at DESC"
+ has_finder :limit, lambda { |limit| {:limit => limit} }
+
+ def self.find_post_by_date_and_slug(year, month, day, slug)
+ begin
+ date = Date.new(year.to_i, month.to_i, day.to_i)
+ rescue ArgumentError => e
+ raise ActiveRecord::RecordNotFound, e.message
+ end
+ self.find(:first, :conditions => ["kind = #{POST} AND DATE(published_at) = ? AND slug = ?", date, slug])
+ end
+
+ def self.find_page_by_slug(slug)
+ self.find(:first, :conditions => ["kind = #{PAGE} AND slug = ?", slug])
+ end
+
+ # Finds all articles that have all specified tags.
+ # param slack allows for partial matches ordered by relevance
+ #
+ # Article.find_all_by_tag_list [Tag, Tag, Tag]
+ # Article.find_all_by_tag_list ["tag1", "tag2", "tag3"]
+ # Article.find_all_by_tag_list "tag1, tag2, tag3"
+ # # => [Article, Article]
+ def self.find_all_by_tag_list(tag_list, slack = 0)
+ if tag_list.is_a?(String)
+ tag_list = Tag.parse_to_tags(tag_list)
+ else
+ tag_list = tag_list.flatten.map { |t| t.is_a?(Tag) ? t : Tag.find_or_create_by_name(t) }
+ end
+ return [] if tag_list.empty?
+ slack = [tag_list.size-1, slack].min
+ order = "#{table_name}.published_at DESC"
+ order = "COUNT(#{table_name}.id) DESC, " + order unless slack == 0
+ find(:all, :readonly => false,
+ :select => "#{table_name}.*",
+ :joins => "INNER JOIN taggings t ON #{table_name}.id = t.article_id",
+ :conditions => ["t.tag_id IN (?)", tag_list], :order => order,
+ :group => "#{table_name}.id HAVING COUNT(#{table_name}.id) >= #{tag_list.size - slack}")
+ end
+
+ def self.search(query)
+ if query.to_s.blank?
+ []
+ else
+ tokens = query.split.collect { |t| "%#{t.downcase}%" }
+ likes = (["(LOWER(title) LIKE ? OR LOWER(body) LIKE ? OR LOWER(extended) LIKE ?)"] * tokens.size).join(" AND ")
+ self.published.find(:all, :conditions => [likes, *tokens.zip(tokens, tokens).flatten])
+ end
+ end
+
+ def post?
+ self.kind == POST
+ end
+
+ def page?
+ self.kind == PAGE
+ end
+
+ def published?
+ !self.published_at.nil?
+ end
+
+ def post_path_params
+ raise ArgumentError, "can't generate post_path_params for articles that are not POST kind" if self.kind != POST
+ [ published_at.year, published_at.month, published_at.mday, slug ]
+ end
+
+ def post_path_params_hash
+ raise ArgumentError, "can't generate post_path_params for articles that are not POST kind" if self.kind != POST
+ { :year => published_at.year, :month => published_at.month, :day => published_at.mday, :slug => slug }
+ end
+
+
+ def content
+ cont = [self.body]
+ cont << self.extended unless self.extended.blank?
+ cont.join("\n\n")
+ end
+
+ ### comments ###
+
+ # answer whether this kind of article supports comments (post vs page)
+ def can_have_comments?
+ self.post?
+ end
+
+ # answer whether comments are open or closed
+ def allows_comments?
+ self.can_have_comments? && self.comment_period_open?
+ end
+
+ def comment_period_open?
+ days = self.comment_period || 0
+ (days < 0) || (Time.now < self.published_at + days.days)
+ end
+
+ ### tag management ###
+
+ def tag_list
+ tags.collect { |t| t.name }.join(", ")
+ end
+
+ def tag_list=(tag_string)
+ if self.new_record?
+ @tag_list = tag_string
+ else
+ Tagging.set_on(self, tag_string)
+ taggings.reset
+ tags.reset
+ end
+ end
+
+ def set_tag_list_after_create
+ self.tag_list = @tag_list if @tag_list
+ end
+
+end
26 app/models/article_sweeper.rb
@@ -0,0 +1,26 @@
+class ArticleSweeper < ActionController::Caching::Sweeper
+ observe Article, Comment, Tagging
+
+ def after_save(record)
+ expire_record(record)
+ end
+
+ def after_destroy(record)
+ expire_record(record)
+ end
+
+ def expire_record(record)
+ if record.is_a?(Article) && record.post?
+ expire_page(hash_for_root_path)
+ expire_page(hash_for_feed_path)
+ end
+ article = record.is_a?(Article) ? record : record.article
+ expire_page(hash_for_post_path(article.post_path_params_hash)) if article.post?
+ expire_page(hash_for_page_path(article)) if article.page?
+ if record.is_a?(Tagging)
+ expire_page(hash_for_tags_path)
+ expire_page(hash_for_tag_path(:tag => record.tag.name))
+ expire_page(hash_for_tag_feed_path(:tag => record.tag.name))
+ end
+ end
+end
46 app/models/comment.rb
@@ -0,0 +1,46 @@
+class Comment < ActiveRecord::Base
+ belongs_to :article, :counter_cache => true
+ belongs_to :user
+
+ validates_existence_of :article
+ validates_existence_of :user, :allow_nil => true
+ validates_presence_of :author_name, :unless => :user_id?
+ # validates_presence_of :author_email, :unless => :user_id?
+ validates_presence_of :body
+
+ before_create :check_comment_expiration
+
+ attr :tofu
+
+ def presentation_class
+ case self.user_id
+ when self.article.user_id
+ "by-author"
+ when nil
+ "by-guest"
+ else
+ "by-user"
+ end
+ end
+
+ def author_link
+ name = self.author_name
+ url = nil
+
+ case
+ when self.user
+ name = self.user.name
+ when self.author_url
+ url = self.author_url
+ end
+ if url.blank?
+ "<span>#{CGI::escapeHTML(name)}</span>"
+ else
+ %Q[<a href="#{CGI::escapeHTML(url)}">#{CGI::escapeHTML(name)}</a>]
+ end
+ end
+
+ def check_comment_expiration
+ raise Article::CommentNotAllowed unless article.comment_period_open?
+ end
+end
3  app/models/reject.rb
@@ -0,0 +1,3 @@
+class Reject < ActiveRecord::Base
+ belongs_to :article
+end
52 app/models/tag.rb
@@ -0,0 +1,52 @@
+class Tag < ActiveRecord::Base
+ has_many :taggings, :dependent => :destroy
+ has_many :articles, :through => :taggings
+
+ validates_uniqueness_of :name
+
+ def self.find_all_popular
+ self.find(:all, :conditions => "taggings_count > 0", :order => "taggings_count DESC, name ASC")
+ end
+
+ class << self
+ # Parses comma separated tag list and returns tags for them.
+ #
+ # Tag.parse_to_tags('a, b, c')
+ # # => [Tag, Tag, Tag]
+ def parse_to_tags(list)
+ find_or_create(parse(list))
+ end
+
+ # Parses a comma separated list of tags into tag names
+ #
+ # Tag.parse('a, b, c')
+ # # => ['a', 'b', 'c']
+ def parse(list)
+ list.gsub(/[^\w\ ,]+/, '').squeeze(" ").downcase.split(",").map(&:strip).reject { |s| s.blank? }.uniq
+ end
+
+ # Returns Tags from an array of tag names
+ #
+ # Tag.find_or_create(['a', 'b', 'c'])
+ # # => [Tag, Tag, Tag]
+ def find_or_create(tag_names)
+ transaction do
+ found_tags = find(:all, :conditions => ['name IN (?)', tag_names])
+ found_tags + (tag_names - found_tags.collect(&:name)).collect { |s| create!(:name => s) }
+ end
+ end
+ end
+
+ def ==(object)
+ super || name == object.to_s
+ end
+
+ def to_s
+ name
+ end
+
+ def to_param
+ name.gsub(/ /,'+')
+ end
+
+end
42 app/models/tagging.rb
@@ -0,0 +1,42 @@
+class Tagging < ActiveRecord::Base
+ belongs_to :article
+ belongs_to :tag, :counter_cache => true
+
+ validates_presence_of :article_id, :tag_id
+
+ class << self
+ # Sets the tags on the article. Only adds new tags and deletes old tags.
+ #
+ # Tagging.set_on @article, 'foo, bar'
+ def set_on(article, tag_list)
+ current_tags = article.tags
+ new_tags = Tag.parse_to_tags(tag_list)
+ remove_from article, (current_tags - new_tags)
+ add_to article, (new_tags - current_tags)
+ end
+
+ # Deletes tags from the article
+ #
+ # Tagging.remove_from @article, [1, 2, 3]
+ # Tagging.remove_from @article, [Tag, Tag, Tag]
+ def remove_from(article, tags)
+ unless tags.blank?
+ tag_ids = tags.collect { |t| t.is_a?(Tag) ? t.id : t }
+ destroy_all ['article_id = ? and tag_id in (?)', article.id, tag_ids] # use destroy_all to maintain counter_cache in tag
+ end
+ end
+
+ # Adds tags to the article
+ #
+ # Tagging.add_to @article, [Tag, Tag, Tag]
+ def add_to(article, tags)
+ unless tags.blank?
+ tags.each do |tag|
+ next if article.tags.include?(tag)
+ create!(:article => article, :tag => tag)
+ end
+ end
+ end
+ end
+
+end
86 app/models/user.rb
@@ -0,0 +1,86 @@
+require 'digest/sha1'
+class User < ActiveRecord::Base
+
+ # prevents a user from submitting a crafted form that bypasses activation
+ # anything else you want your user to change should be added here.
+ attr_accessible :name, :login, :email, :password, :password_confirmation
+
+ has_many :articles
+
+
+ # Virtual attribute for the unencrypted password
+ attr_accessor :password
+
+ validates_presence_of :name
+ validates_presence_of :login
+ validates_presence_of :email
+ validates_presence_of :password, :if => :password_required?
+ validates_presence_of :password_confirmation, :if => :password_required?
+ validates_length_of :password, :within => 4..40, :if => :password_required?
+ validates_confirmation_of :password, :if => :password_required?
+ validates_length_of :login, :within => 3..40
+ validates_length_of :email, :within => 3..100
+ validates_uniqueness_of :login, :email, :case_sensitive => false
+ before_save :encrypt_password
+
+
+
+ ### restful_authentication ###
+
+ # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
+ def self.authenticate(login, password)
+ u = find_by_login(login) # need to get the salt
+ u && u.authenticated?(password) ? u : nil
+ end
+
+ # Encrypts some data with the salt.
+ def self.encrypt(password, salt)
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
+ end
+
+ # Encrypts the password with the user salt
+ def encrypt(password)
+ self.class.encrypt(password, salt)
+ end
+
+ def authenticated?(password)
+ crypted_password == encrypt(password)
+ end
+
+ def remember_token?
+ remember_token_expires_at && Time.now.utc < remember_token_expires_at
+ end
+
+ # These create and unset the fields required for remembering users between browser closes
+ def remember_me
+ remember_me_for 2.weeks
+ end
+
+ def remember_me_for(time)
+ remember_me_until time.from_now.utc
+ end
+
+ def remember_me_until(time)
+ self.remember_token_expires_at = time
+ self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
+ save(false)
+ end
+
+ def forget_me
+ self.remember_token_expires_at = nil
+ self.remember_token = nil
+ save(false)
+ end
+
+ protected
+ # before filter
+ def encrypt_password
+ return if password.blank?
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
+ self.crypted_password = encrypt(password)
+ end
+
+ def password_required?
+ crypted_password.blank? || !password.blank?
+ end
+end
16 app/views/admin/articles/_form.html.erb
@@ -0,0 +1,16 @@
+ <dl>
+ <dt>Title</dt>
+ <dd><%= form.text_field :title %></dd>
+ <dt>Slug</dt>
+ <dd><%= form.text_field :slug %></dd>
+ <dt>Tags</dt>
+ <dd><%= form.text_field :tag_list %></dd>
+ <dt>Kind</dt>
+ <dd><%= form.select :kind, [["Post", Article::POST], ["Page", Article::PAGE]] %></dd>
+ <dt>Comment period</dt>
+ <dd><%= form.select :comment_period, comment_period_options %></dd>
+ <dt>Body</dt>
+ <dd><%= form.text_area :body %></dd>
+ <dt>Extended Body</dt>
+ <dd><%= form.text_area :extended %></dd>
+ </dl>
11 app/views/admin/articles/edit.html.erb
@@ -0,0 +1,11 @@
+<h1>Edit Article</h1>
+<%= link_to 'List', admin_articles_path %> |
+<%= link_to 'Show', [:admin, @article] %>
+
+<%= error_messages_for :article %>
+
+<% form_for([:admin, @article]) do |form| %>
+<%= render :partial => 'form', :object => form %>
+<%= form.submit "Save new version", :id => nil %>
+<%= form.submit "Save and keep same version", :id => nil %>
+<% end %>
32 app/views/admin/articles/index.html.erb
@@ -0,0 +1,32 @@
+<h1>Listing articles</h1>
+
+<table>
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Title</th>
+ <th>Tags</th>
+ <th>Published At</th>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<% for article in @articles -%>
+ <tr>
+ <td><%= article.id %></td>
+ <td><%= h article.title %></td>
+ <td><%= h article.tag_list %></td>
+ <td><%= article.published_at && article.published_at.to_s(:db) %></td>
+ <td><%= link_to 'Show', admin_article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_admin_article_path(article) %></td>
+ <td><%= link_to 'Delete', admin_article_path(article), :confirm => 'Are you sure?', :method => :delete %></td>
+ </tr>
+<% end -%>
+ </tbody>
+</table>
+
+<br />
+
+<%= link_to 'New article', new_admin_article_path %>
10 app/views/admin/articles/new.html.erb
@@ -0,0 +1,10 @@
+<h1>New Article</h1>
+<%= link_to 'List', admin_articles_path %>
+
+<%= error_messages_for :article %>
+
+<% form_for([:admin, @article]) do |form| %>
+<%= render :partial => 'form', :object => form %>
+<p><%= form.submit "Create" %></p>
+<% end %>
+
32 app/views/admin/articles/show.html.erb
@@ -0,0 +1,32 @@
+<h2>Article</h2>
+<%= link_to 'List', admin_articles_path %> |
+<%= link_to 'Edit', edit_admin_article_path(@article) %> |
+<%= link_to 'Delete', admin_article_path(@article), :method => :delete, :confirm => "Delete this article?" %>
+<dl>
+ <dt>Title</dt>
+ <dd><%= if_blank(h(@article.title), "-") %></dd>
+ <dt>Slug</dt>
+ <dd><%= if_blank(h(@article.slug), "-") %></dd>
+ <dt>Tags</dt>
+ <dd><%= if_blank(h(@article.tag_list), "-") %></dd>
+ <dt>Kind</dt>
+ <dd><%= %w(Post Page)[@article.kind] %></dd>
+ <dt>Comment period</dt>
+ <dd><%= comment_period_options.detect { |opt| opt.last == @article.comment_period }.first rescue "bad value" %></dd>
+ <dt>Body</dt>
+ <dd><%= @article.body.blank? ? "-" : markdown(@article.body) %></dd>
+ <dt>Extended Body</dt>
+ <dd><%= @article.extended.blank? ? "-" : markdown(@article.extended) %></dd>
+ <dt>Published at</dt>
+ <dd><%= @article.published_at.nil? ? "never" : @article.published_at.to_s(:db) %></dd>
+ <dt>Created at</dt>
+ <dd><%= @article.created_at.nil? ? "never" : @article.created_at.to_s(:db) %></dd>
+ <dt>Updated at</dt>
+ <dd><%= @article.updated_at.nil? ? "never" : @article.updated_at.to_s(:db) %></dd>
+</dl>
+
+<h3>Comments</h3>
+
+<ul id="comments" class="comments">
+ <%= render :partial => "admin/comments/comment", :collection => @comments %>
+</ol>
15 app/views/admin/comments/_comment.html.erb
@@ -0,0 +1,15 @@
+ <li id="<%= dom_id(comment) %>" class="stripe-<%= cycle('even', 'odd') %>">
+ comment <%= comment.article.comments.index(comment) + 1 %> of <%= comment.article.comments_count %>
+ in <%= link_to h(comment.article.title), post_path(*comment.article.post_path_params) %><br/>
+ <blockquote><%= h comment.body %></blockquote>
+ by <%= h if_blank(comment.author_name, "(no name)") %> &mdash;
+ <%= h if_blank(comment.author_email, "(no email)") %> &mdash;
+ <%= h if_blank(comment.author_url, "(no url)") %> [<%= if_blank(comment.author_ip, "no IP") %>]
+ at <%= comment.created_at.to_s(:db) %>
+ <br/>
+ <% p = comment.article.post_path_params %>
+ <%= link_to "View", post_path(p[0], p[1], p[2], p[3],
+ :anchor => dom_id(comment)), :title => "view" %>
+ <%= link_to "Delete", admin_comment_path(comment),
+ :method => :delete, :confirm => "Delete comment?", :title => "delete" %>
+ </li>
6 app/views/admin/comments/index.html.erb
@@ -0,0 +1,6 @@
+<h2>Comments</h2>
+<ul id="comments" class="comments">
+ <%= render :partial => "comment", :collection => @comments %>
+</ol>
+
+<%= will_paginate @comments %>
12 app/views/admin/panel/index.html.erb
@@ -0,0 +1,12 @@
+<h1>Administration Panel</h1>
+<ul>
+ <li><%= link_to "Create new article", new_admin_article_path %></li>
+ <li><%= link_to "Manage articles", admin_articles_path %></li>
+</ul>
+<ul>
+ <li><%= link_to "Manage comments", admin_comments_path %></li>
+</ul>
+<ul>
+ <li>Manage tags</li>
+</ul>
+<p><%= link_to "Logout", logout_path %></p>
12 app/views/blog/_article.atom.builder
@@ -0,0 +1,12 @@
+feed.entry(article, :published => article.published_at,
+ :url => post_url(*article.post_path_params) ) do |entry|
+ entry.title(article.title)
+ entry.author do |author|
+ author.name(article.user.name)
+ end
+ article.tags.each do |tag|
+ entry.category(:term => tag.name)
+ end
+ entry.summary(markdown(article.body), :type => 'html')
+ entry.content(markdown(article.content), :type => 'html')
+end
80 app/views/blog/_article.html.erb
@@ -0,0 +1,80 @@
+<% mode = nil unless local_assigns.has_key?(:mode) -%>
+<div class="hentry" id="<%= dom_id(article) %>">
+ <h2 class="entry-title"><%= link_to_article(article) %></h2>
+ <div class="vcard">
+ <p>&mdash; <%= article.published_at.strftime "%B %e, %Y at %H:%M %Z" %></p>
+ </div>
+ <br class="clear" />
+
+ <div class="entry-content">
+<% if mode == :list -%>
+<!-- begin body -->
+<%= markdown article.body %>
+<% unless article.extended.blank? %>
+<p><a href="<%= post_path(*article.post_path_params) %>">Continue reading...</a></p>
+<% end %>
+<!-- end body -->
+<% else -%>
+<!-- begin content -->
+<%= markdown article.content %>
+<!-- end content -->
+<% end -%>
+ </div>
+
+<% if article.post? %>
+ <p class="meta"><%= pluralize(article.comments_count, "comment") %> &mdash; <%= links_to_tags article.tags %></p>
+
+<% if mode != :list -%>
+ <h5><a name="comments">Comments</a></h5>
+ <ol id="comments" class="comments">
+<%= render :partial => "comment", :collection => article.comments %>
+ </ol>
+
+ <% if article.allows_comments? -%>
+ <% form_for [article, article.comments.new] do |comment_form| %>
+ <fieldset>
+ <legend>Add Comment</legend>
+ <p>
+ <%= comment_form.text_field :author_name, :tabindex => 1 %>
+ <label class="text" for="comment_author">Name</label>
+ </p>
+ <p>
+ <%= comment_form.text_field :author_email, :tabindex => 2 %>
+ <label class="text" for="comment_author_email">Email Address (not shown)</label>
+ </p>
+ <p>
+ <%= comment_form.text_field :author_url, :tabindex => 3 %>
+ <label class="text" for="comment_author_url">Website</label>
+ </p>
+ <p>
+ <%= comment_form.text_area :body, :style => "width:0px; height:0px; margin:0; border:0; padding:0;" %><br />
+ <%= comment_form.text_area :tofu, :tabindex => 4 %><br />
+ <small>Comments are styled using <a href="http://daringfireball.net/projects/markdown/">Markdown</a>.</small>
+ </p>
+ <div class="formactions">
+ <input type="button" value="Preview comment" id="preview_button" />
+ </div>
+ <br />
+ <div id="preview" style="display:none">
+ <ol class="comments">
+ <li class="comment preview">
+ <div class="author">
+ <cite><span id="comment_preview_author" class="author"><em>Your name</em></span></cite>
+ </div>
+ <div id="comment_preview_body" class="content">
+ <em>Your pithy comments...</em>
+ </div>
+ </li>
+ </ol>
+ <div class="formactions">
+ <input type="submit" value="Post comment" class="submit" />
+ </div>
+ </div>
+ </fieldset>
+ <% end %>
+ <% else %>
+ <p>Sorry, comments for this article are closed.</p>
+ <% end %>
+<% end %>
+<% end %>
+</div>
6 app/views/blog/_article_list.html.erb
@@ -0,0 +1,6 @@
+<dl class="article_list">
+<% article_list.each do |article| -%>
+ <dt><%= link_to_article(article) %></dt>
+ <dd class="meta"><%= article.published_at.strftime("%Y-%m-%d") %> <%= links_to_tags article.tags %></dd>
+<% end -%>
+</dl>
10 app/views/blog/_comment.html.erb
@@ -0,0 +1,10 @@
+ <li class="comment <%= comment.presentation_class %>" id="<%= dom_id(comment) %>">
+ <a name="<%= dom_id(comment) %>"></a>
+ <div class="author">
+ <cite><span class="author"><%= comment.author_link %></span></cite> &#150;
+ <abbr title="<%= comment.created_at.to_s(:db) %>"><span class="date"><%= comment.created_at.to_s(:db) %></span></abbr>
+ </div>
+ <div class="content">
+ <%= markdown(comment.body) %>
+ </div>
+ </li>
5 app/views/blog/index.atom.builder
@@ -0,0 +1,5 @@
+atom_feed(:schema_date => "2006-02-27") do |feed|
+ feed.title SITE_TITLE
+ feed.updated @articles.first.published_at
+ render :partial => 'article', :collection => @articles, :locals => { :feed => feed }
+end
6 app/views/blog/index.html.erb
@@ -0,0 +1,6 @@
+<%= render :partial => "article", :collection => @articles, :locals => {:mode => :list} %>
+
+<h3>Archives</h3>
+<ul id="archives">
+ <%= archive_list %>
+</ul>
1  app/views/blog/results.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "article_list", :object => @articles %>
3  app/views/blog/show.html.erb
@@ -0,0 +1,3 @@
+<%= render :partial => "article", :object => @article %>
+
+<%= javascript_include_tag 'showdown', 'showdown-gui' %>
5 app/views/blog/tag.atom.builder
@@ -0,0 +1,5 @@
+atom_feed(:schema_date => "2006-02-27") do |feed|
+ feed.title h("#{SITE_TITLE} - #{params[:tag]}")
+ feed.updated @articles.first.published_at
+ render :partial => 'article', :collection => @articles, :locals => { :feed => feed }
+end
10 app/views/blog/tag.html.erb
@@ -0,0 +1,10 @@
+<h2 class="tag">Articles tagged by <em><%= h @tag.name %></em></h2>
+
+<p class="clean_link"><a href="<%= tag_feed_path(@tag) %>"><%= image_tag("feed16x16.png") %> Subscribe</a> to feed for this tag.</p>
+
+<dl class="results">
+<% @articles.each do |article| -%>
+ <dt><%= link_to_article(article) %></dt>
+ <dd class="meta"><%= article.published_at.strftime("%Y-%m-%d") %> <%= links_to_tags article.tags %></dd>
+<% end -%>
+</dl>
5 app/views/blog/tags.html.erb
@@ -0,0 +1,5 @@
+<ul id="tags">
+<% @tags.each do |tag| -%>
+ <li><%= link_to h(tag.name), tag_path(tag) %> (<%= tag.taggings_count %>)</li>
+<% end -%>
+</ul>
21 app/views/layouts/admin.html.erb
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
+ <title>Teldra administration for "<%= ::SITE_TITLE %>"</title>
+ <%= stylesheet_link_tag 'admin' %>
+ <%= javascript_include_tag :defaults %>
+</head>
+<body>
+ <div id="header">
+ <p><%= link_to "Teldra Admin Panel", panel_path %> :: Welcome, <%= current_user_name %></p>
+ </div>
+
+<%= flash_div %>
+
+<%= yield %>
+
+</body>
+</html>
58 app/views/layouts/blog.html.erb
@@ -0,0 +1,58 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
+ <title><%= h doc_title %></title>
+ <%= stylesheet_link_tag 'blog' %>
+</head>
+
+<body>
+
+<div id="container">
+ <div id="header">
+ <h1><span><a href="/">Teldra</a></span></h1>
+ <h2>the danceable demo version</h2>
+ </div>
+
+ <div id="page">
+ <div id="content">
+<%= flash_div %>
+<%= yield %>
+ </div>
+
+ <div id="sidebar">
+ <div class="sidebar-node search" id="search">
+ <form action="<%= search_path %>" id="search_form" method="get" name="search_form">
+ <p><input type="text" id="q" name="q" value="search" onfocus="this.value = '';" /></p>
+ </form>
+ </div>
+
+ <div class="sidebar-node" id="about">
+ <h3>About</h3>
+ <p>Demo software for <em>The Great Test Framework Dance-off</em> presentation at RailsConf 2008 by Josh Susser</p>
+ <p><a href="/page/copyright">Copyright</a></p>
+ </div>
+
+ <div class="sidebar-node" id="tag_cloud">
+ <h3>Tags</h3>
+ <p id="tags"><%= tag_cloud %></p>
+ </div>
+
+ </div>
+
+ <div class="clear" />
+ </div>
+
+ <div id="footer">
+ <hr />
+ <p><a href="/"><%= SITE_TITLE %></a></p>
+ <ul>
+ <li>powered by Teldra /
+ styled with <a href="http://quotedprintable.com/pages/scribbish">scribbish</a></li>
+ </ul>
+ </div>
+</div>
+
+</body>
+</html>
12 app/views/sessions/new.html.erb
@@ -0,0 +1,12 @@
+<% form_tag session_path do -%>
+<p><label for="login">Login</label><br/>
+<%= text_field_tag 'login' %></p>
+
+<p><label for="password">Password</label><br/>
+<%= password_field_tag 'password' %></p>
+
+<p><label for="remember_me">Remember me:</label>
+<%= check_box_tag 'remember_me' %></p>
+
+<p><%= submit_tag 'Log in' %></p>
+<% end -%>
573 app/views/stylesheets/blog.ncss
@@ -0,0 +1,573 @@
+/*--------------------------------------------------------------
+ all.css
+ merges all stylesheets; sets defaults for bare elements
+ --------------------------------------------------------------*/
+
+/* --- layout.css --- */
+
+/**
+ * Controls the main layout (width, height, margin, padding)
+ *
+ * #container
+ * #header
+ * #page
+ * #content
+ * #sidebar
+ * #footer
+ */
+body {
+ text-align: center;
+ margin: 0; padding: 0.6em 1em 1em 1em;
+}
+
+#container {
+ width: 900px;
+ text-align: left;
+ margin: 0 auto; padding: 0;
+}
+
+#header {
+ height: 62px;
+ margin: 0 -0.5px 12px 0; padding: 0;
+}
+
+#page {}
+
+#content {
+ float: left;
+ width: 662px;
+ padding: 0 0 0 5px;
+}
+
+#sidebar {
+ float: right;
+ text-align: left;
+ width: 200px;
+ padding-left: 15px;
+ border-left: 1px dotted #ddd;
+}
+
+#footer {
+ height: 40px;
+ margin: 10px 0 0; padding: 10px 0 0;
+ clear:both;
+}
+
+/* --- content.css --- */
+
+/*--------------------------------------------------------------
+ Header
+ --------------------------------------------------------------*/
+
+#header {
+ background: url("/images/header_shadow.gif") repeat-x left bottom;
+}
+
+#header a:link, #header a:visited {
+ color: #000;
+ text-decoration: none;
+}
+#header a:hover, #header a:active {
+ color:#039; background: transparent;
+ text-decoration: none;
+}
+#header h1 {
+ font: bold 400% courier, "courier new", monaco, monospace;
+ letter-spacing: -1px;
+ margin: 0; padding: 0 3px;
+ float: left;
+}
+#header h2 {
+ font: normal 12px verdana, arial, sans-serif;
+ margin: 2.5em 0 0 0.8em;
+ float: left;
+}
+
+/*--------------------------------------------------------------
+ Content
+ --------------------------------------------------------------*/
+
+#content {}
+
+#content h1,
+#content h2,
+#content h3,
+#content h4,
+#content h5 {
+ font-family: "lucidamac bold", "lucida grande", arial, sans-serif;
+ letter-spacing: -1px;
+}
+
+#content h1 {
+ font-size: 24px;
+ margin: 0 0 0.3em;
+}
+
+#content h2 {
+ font-size: 22px;
+ margin: 0 0 0.3em;
+}
+
+#content h3 {
+ font-size: 20px;
+ margin: 1.2em 0 0.3em;
+}
+
+#content h4 {
+ font-size: 18px;
+ margin: 1.2em 0 0.3em;
+ border-bottom: 1px dotted #bbb;
+}
+
+#content h5 {
+ font-size: 18px;
+ background: #ffd;
+ margin: 1.2em 0 0.3em;
+ border-bottom: 1px dotted #aaa;
+}
+
+#content p {
+ line-height: 15px;
+ margin: 0 0 1.2em;
+}
+
+#content ul,
+#content ol {
+ margin: 1em;
+ padding:0;
+}
+
+#content ul {
+ list-style-type: square;
+}
+
+#content li {
+ line-height: 15px;
+ margin: 0 0 0 1em; padding: 0;
+}
+
+#content blockquote {
+ color: #555;
+ border-left: 5px solid #ccc;
+ margin: 1.3em 1em; padding: 0 1em;
+}
+
+#content code {
+ font: normal 12px "bitstream vera sans mono", monaco "lucida console", "courier new", courier, serif;
+ color: #000;
+ background: #eee;
+}
+
+#content pre {
+ color: #000;
+ background: #ddd;
+ overflow: auto;
+ font: normal 12px "bitstream vera sans mono", monaco "lucida console", "courier new", courier, serif;
+ margin: 0.9em 0; padding: 8px;
+}
+
+#content pre code {
+ background: #ddd;
+}
+
+/* Article Entries - class names based on http://microformats.org/wiki/hatom] */
+#content .hentry {
+ margin: 0 0 3em 0;
+}
+
+#content .hentry .entry-title {
+ font-size: 24px;
+ line-height: 94%;
+ letter-spacing: -0.75px;
+ margin: 0;
+}
+
+#content .hentry .entry-title a:link,
+#content .hentry .entry-title a:visited {
+ color: #039;
+ text-decoration: none;
+}
+
+#content .hentry .entry-title a:hover,
+#content .hentry .entry-title a:active {
+ color: #000;
+ background: transparent;
+}
+
+#content .hentry .entry-title .comment_count { display: none; }
+
+#content .hentry .vcard,
+#content .hentry .published {
+ float: left;
+ color: #a9a9a9;
+ font: normal 12px "lucidamac bold", "lucida grande", arial, verdana, sans-serif;
+ letter-spacing: -1px;
+ margin: 1px 0 0.6em 2px;
+}
+
+#content .hentry .fn {
+ font-weight:bold;
+ margin-right: 5px;
+}
+
+#content .hentry .fn a,
+#content .hentry .fn a:hover {
+ color: #aaa; background: transparent;
+ text-decoration: none;
+}
+
+#content .hentry .clear { clear: both; }
+
+#content .hentry .entry-content { }
+
+#content .hentry ul.meta {
+ font-size: 10px;
+ background: #eee;
+ margin: 0; padding: 5px;
+ border: 1px solid #ddd;
+ list-style-type: none;
+}
+
+#content .hentry ul.meta li {
+ line-height: 13px;
+ margin: 0; padding: 0;
+}
+
+#content .hentry .meta a:link,
+#content .hentry .meta a:visited {
+ color: #555;
+ text-decoration: none;
+ border-bottom: 1px dotted #aaa;
+}
+
+#content .hentry .meta a:hover,
+#content .hentry .meta a:active {
+ color: #fff;
+ background: #666;
+}
+
+
+/* Comments and Trackbacks */
+#content ol.comments {
+ list-style-type: none;
+ margin: 0; padding: 0;
+}
+
+#content li.comment {
+ border: 2px solid #ddd;
+ margin: 0 0 1.5em; padding: 1em;
+}
+
+#content li.by-author {
+ border-color: #039;
+ background-color: #f7f7ff;
+}
+
+#content li.comment.preview {
+ background: #ffc;
+ border: 3px solid #fab444;
+ margin: 0 0 1.5em; padding: 1em;
+}
+
+#content li.comment .author {
+ font-weight: bold;
+ margin-bottom: 1em;
+}
+
+#content li.by-author .author {
+}
+
+#content li.comment .author cite {
+ font-size: 16px;
+ letter-spacing: -1px;
+}
+
+#content li.comment .author abbr { color: #999; }
+
+#content li.comment .author .gravatar {
+ margin: 0 0 0.5em 0.5em;
+ float: right;
+}
+
+#content li.comment .author div {
+ margin: 0 0 0.5em 0.5em;
+ width: 60px; height: 60px;
+ background: url("/images/gravatar.gif") no-repeat left top;
+ float: right;
+}
+
+#content form#comment-form {
+ background: #f2f2f2;
+ border-top: 1px solid #ddd;
+ padding: 1em 0.5em;
+}
+
+#content form#comment-form fieldset {
+ border: none;
+}
+
+#content form#comment-form legend {
+ display: none;
+}
+
+#content form#comment-form label {
+ font-weight: bold;
+}
+
+#content form#comment-form textarea {
+ width: 90%; height: 150px;
+ padding: 3px;
+}
+
+/*--------------------------------------------------------------
+ Sidebar
+ --------------------------------------------------------------*/
+
+#sidebar {
+ font-size: 11px;
+}
+
+#sidebar h3 {
+ font: bold 14px "lucidamac bold", "lucida grande", verdana, arial, helvetica, sans-serif;
+ margin: 0 0 0.5em;
+}
+
+#sidebar h3 a:link,
+#sidebar h3 a:visited {
+ color: #000; text-decoration: none;
+}
+
+#sidebar h3 a:hover,
+#sidebar h3 a:active {
+ background: transparent; text-decoration: underline;
+}
+
+#sidebar ul {
+ list-style-type: none;
+ margin: 0 0 2em; padding: 0;
+}
+
+#sidebar li {
+ margin: 0;
+ padding: 1px 0;
+}
+
+#sidebar em { font-style: normal; }
+
+/* Live-search and results */
+#sidebar .search p {
+ margin: 0 0 1em 0;
+}
+
+#sidebar .search p input {
+ font-size: 11px; width: 92%;
+}
+
+/*--------------------------------------------------------------
+ Footer
+ --------------------------------------------------------------*/
+
+#footer {
+ border-top: 1px solid #ccc;
+ font-size: 90%;
+ color: #aaa;
+}
+
+#footer a:link,
+#footer a:visited {
+ color: #aaa;
+}
+
+#footer a:hover,
+#footer a:active {
+ color: #fff;
+ background: #666;
+ text-decoration: none;
+}
+
+#footer hr {
+ display: none;
+}
+
+#footer p {
+ width: 40%;
+ float: left;
+ margin: 0; padding: 0;
+}
+
+#footer ul {
+ width: 40%;
+ margin: 0; padding: 0;
+ list-style-type: none;
+ text-align: right;
+ float: right;
+}
+
+#footer li {
+ margin: 0; padding: 0 0 0 1em;
+ display: inline;
+}
+
+/*--------------------------------------------------------------
+ application.css
+ sets defaults for bare elements
+ --------------------------------------------------------------*/
+
+@media print { #sidebar { display: none; }
+ #content { float: none; width: 90%; }
+ #content pre { color: #000; background: #eee; }
+ #content form.comments { display: none; } }
+
+body {
+ /*background: url(../../images/theme/background.gif) repeat-x left top;*/
+ font: normal 12px "lucida grande", verdana, sans-serif;
+}
+
+input,
+textarea { font: normal 12px "bitstream vera sans", verdana, sans-serif; }
+
+abbr { border: none; }
+cite { font-style: normal; }
+a img { border: none; padding: 0; margin: 0; }
+
+a:link, a:visited { color: #039; text-decoration: underline; }
+a:hover, a:active { color: #00afff; text-decoration: none; }
+
+/* http://longren.org/2006/09/27/wrapping-text-inside-pre-tags */
+pre {
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap !important;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ word-wrap: break-word;
+}
+
+/* --- syntax.css --- */
+/*--------------------------------------------------------------
+ Syntax Highlighting - contributed by James Wilford
+ Closely matches TextMate's "All Hallow's Eve" theme
+ --------------------------------------------------------------*/
+
+.CodeRay {
+ border:1px solid #ccc;
+ border-right:1px dotted #ccc;
+ background-color:#444;
+ margin:0 0 10px 0;
+ position:relative;
+ font-size:14px;
+ overflow:auto;
+ width:657px;
+ padding:0;
+ z-index:1;
+}
+.CodeRayMoused {
+ border:solid 1px #ccc;
+ width:900px;
+ z-index:3;
+}
+
+div.CodeRay { }
+span.CodeRay { }
+table.CodeRay { border-collapse:collapse; }
+
+
+table.CodeRay td { vertical-align:top; }
+
+.CodeRay .line_numbers, .CodeRay .no {
+ background-color:#e2e2e2;
+ text-align:right;
+ padding:3px 5px;
+ color:#888;
+}
+.CodeRay .line_numbers tt { font-weight:bold; }
+.CodeRay .no {
+ background-color:#e2e2e2;
+ text-align:right;
+ padding:3px 5px;
+ color:#888;
+}
+.CodeRay .code { width:100%; }
+
+ol.CodeRay { font-size:12pt; }
+ol.CodeRay li { white-space:pre; }
+
+.af { color:#00C; }
+.an { color:#007; }
+.av { color:#700; }
+.aw { color:#C00; }
+.bi { color:#509; font-weight:bold; }
+.c { color:#93c; }
+
+.ch { color:#04D; }
+.ch .k { color:#04D; }
+.ch .dl { color:#039; }
+
+.cl { color:#B06; font-weight:bold; }
+.co { color:#036; font-weight:bold; }
+.cr { color:#0A0; }
+.cv { color:#369; }
+.df { color:#099; font-weight:bold; }
+.di { color:#088; font-weight:bold; }
+.dl { color:black; }
+.do { color:#970; }
+.ds { color:#D42; font-weight:bold; }
+.e { color:#666; font-weight:bold; }
+.en { color:#800; font-weight:bold; }
+.er { color:#F00; background-color:#FAA; }
+.ex { color:#F00; font-weight:bold; }
+.fl { color:#60E; font-weight:bold; }
+.fu { color:#06B; font-weight:bold; }
+.gv { color:#d70; font-weight:bold; }
+.hx { color:#058; font-weight:bold; }
+.i { color:#00D; font-weight:bold; }
+.ic { color:#B44; font-weight:bold; }
+
+.il { color:#fff; background:#000; }
+.il .co { color:#fff; }
+.il .dl { font-weight:bold; color:#fff; }
+.il .sy { color:#3387cc; }
+.il .s { background-color:transparent; }
+.il .s .s { background-color:transparent; }
+.il .s .s .s { background-color:transparent; }
+.il .s .k { color:#66cc33; }
+.il .s .dl { color:#66cc33; }
+
+.in { color:#B2B; font-weight:bold; }
+.iv { color:#33B; }
+.la { color:#970; font-weight:bold; }
+.lv { color:#963; }
+.oc { color:#40E; font-weight:bold; }
+.on { color:#000; font-weight:bold; }
+.op { }
+.pc { color:#37a; font-weight:bold; }
+.pd { color:#369; font-weight:bold; }
+.pp { color:#579; }
+.pt { color:#339; font-weight:bold; }
+.r { color:#c73; font-weight:bold; }
+
+.rx { background-color:#fff0ff; }
+.rx .k { color:#808; }
+.rx .dl { color:#404; }
+.rx .mod { color:#C2C; }
+.rx .fu { color:#404; font-weight:bold; }
+
+.s { background-color:#fff0f0; }
+.s .s { background-color:#ffe0e0; }
+.s .s .s { background-color:#ffd0d0; }
+.s .k { color:#D20; }
+.s .dl { color:#710; }
+
+.sh { background-color:#f0fff0; }
+.sh .k { color:#2B2; }
+.sh .dl { color:#161; }
+
+.sy { color:#A60; }
+.sy .k { color:#A60; }
+.sy .dl { color:#630; }
+
+.ta { color:#070; }
+.tf { color:#070; font-weight:bold; }
+.ts { color:#D70; font-weight:bold; }
+.ty { color:#339; font-weight:bold; }
+.v { color:#036; }
+.xt { color:#444; }
108 config/boot.rb
@@ -0,0 +1,108 @@
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
+
+RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
+
+module Rails
+ class << self
+ def boot!
+ unless booted?
+ preinitialize
+ pick_boot.run
+ end
+ end
+
+ def booted?
+ defined? Rails::Initializer
+ end
+
+ def pick_boot
+ (vendor_rails? ? VendorBoot : GemBoot).new
+ end
+
+ def vendor_rails?
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
+ end
+
+ def preinitialize
+ load(preinitializer_path) if File.exists?(preinitializer_path)
+ end
+
+ def preinitializer_path
+ "#{RAILS_ROOT}/config/preinitializer.rb"
+ end
+ end
+
+ class Boot
+ def run
+ load_initializer
+ Rails::Initializer.run(:set_load_path)
+ end
+ end
+
+ class VendorBoot < Boot
+ def load_initializer
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ end
+ end
+
+ class GemBoot < Boot
+ def load_initializer
+ self.class.load_rubygems
+ load_rails_gem
+ require 'initializer'
+ end
+
+ def load_rails_gem
+ if version = self.class.gem_version
+ gem 'rails', version
+ else
+ gem 'rails'
+ end
+ rescue Gem::LoadError => load_error
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+ exit 1
+ end
+
+ class << self
+ def rubygems_version
+ Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
+ end
+
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
+ end
+ end
+
+ def load_rubygems
+ require 'rubygems'
+
+ unless rubygems_version >= '0.9.4'
+ $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ exit 1
+ end
+
+ rescue LoadError
+ $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
+
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*'([!~<>=]*\s*[\d.]+)'/
+ end
+
+ private
+ def read_environment_rb
+ File.read("#{RAILS_ROOT}/config/environment.rb")
+ end
+ end
+ end
+end
+
+# All that for this:
+Rails.boot!
23 config/database.yml.example
@@ -0,0 +1,23 @@
+development:
+ adapter: mysql
+ encoding: utf8
+ database: teldra_development
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+
+test:
+ adapter: mysql
+ encoding: utf8
+ database: teldra_test
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+
+production:
+ adapter: mysql
+ encoding: utf8
+ database: teldra_production
+ username: root
+ password:
+ socket: /tmp/mysql.sock
59 config/environment.rb
@@ -0,0 +1,59 @@
+# Be sure to restart your server when you modify this file
+
+# Uncomment below to force Rails into production mode when
+# you don't control web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+ # See Rails::Configuration for more options.
+
+ # Skip frameworks you're not going to use (only works if using vendor/rails).
+ # To use Rails without a database, you must remove the Active Record framework
+ # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
+
+ # Only load the plugins named here, in the order given. By default, all plugins
+ # in vendor/plugins are loaded in alphabetical order.
+ # :all can be used as a placeholder for all plugins not explicitly named
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
+
+ # Force all environments to use the same logger level
+ # (by default production uses :info, the others :debug)
+ # config.log_level = :debug
+
+ # Your secret key for verifying cookie session data integrity.
+ # If you change this key, all old sessions will become invalid!
+ # Make sure the secret is at least 30 characters and all random,
+ # no regular words or you'll be exposed to dictionary attacks.
+ config.action_controller.session = {
+ :session_key => '_teldra_session',
+ :secret => '7e7e6c3efbaaa6374bc223f4754d35e0ed37d9fb0b8726d9ee433523627bed3c418c014c207fe546f9d3b3eae3b5b5d0fe2dc2fb0ef21f3ea3859c0af7dcdc55'
+ }
+
+ # Use the database for sessions instead of the cookie-based default,
+ # which shouldn't be used to store highly confidential information
+ # (create the session table with 'rake db:sessions:create')
+ # config.action_controller.session_store = :active_record_store
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Activate observers that should always be running
+ # config.active_record.observers = :cacher, :garbage_collector
+
+ # Make Active Record use UTC-base instead of local time
+ # config.active_record.default_timezone = :utc
+end
18 config/environments/development.rb
@@ -0,0 +1,18 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request. This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs = true
+config.action_controller.perform_caching = false
+config.action_view.cache_template_extensions = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
18 config/environments/development_cached.rb
@@ -0,0 +1,18 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request. This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and enable caching
+config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs = true
+config.action_controller.perform_caching = true # do cache
+config.action_view.cache_template_extensions = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
19 config/environments/production.rb
@@ -0,0 +1,19 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Use a different logger for distributed setups
+# config.logger = SyslogLogger.new
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching = true
+config.action_controller.page_cache_directory = File.join(RAILS_ROOT, 'public', 'cache')
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host = "http://assets.example.com"
+
+# Disable delivery errors, bad email addresses will be ignored
+# config.action_mailer.raise_delivery_errors = false
22 config/environments/test.rb
@@ -0,0 +1,22 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
+config.cache_classes = true
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching = false
+
+# Disable request forgery protection in test environment
+config.action_controller.allow_forgery_protection = false
+
+# Tell ActionMailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
22 config/geminstaller.yml
@@ -0,0 +1,22 @@
+---
+defaults:
+ install_options: "--include-dependencies --no-rdoc --no-ri"
+gems:
+- name: BlueCloth
+ version: 1.0.0
+- name: chronic
+ version: 0.2.3
+- name: has_finder
+ version: 0.1.5
+- name: highline
+ version: 1.4.0
+- name: mocha
+ version: 0.5.5
+- name: rake
+ version: 0.8.0
+- name: redgreen
+ version: 1.2.2
+- name: termios
+ version: 0.9.4
+- name: test-spec
+ version: 0.4.0
2  config/initializers/dependencies.rb
@@ -0,0 +1,2 @@
+require 'has_finder'
+require 'will_paginate'
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
1  config/initializers/site_settings.rb
@@ -0,0 +1 @@
+SITE_TITLE = "has_many :through"
37 config/routes.rb
@@ -0,0 +1,37 @@
+ActionController::Routing::Routes.draw do |map|
+
+ map.root :controller => 'blog'
+ map.feed 'feed.atom', :controller => 'blog', :format => 'atom'
+ map.with_options :controller => 'blog' do |blog|
+ blog.post ':year/:month/:day/:slug', :action => 'show',
+ :year => /\d{4}/, :month => /\d{1,2}/, :day => /\d{1,2}/
+ blog.page 'page/:slug', :action => 'page'
+ end
+ map.search 'search', :controller => 'blog', :action => 'search'
+ map.tags 'tags', :controller => 'blog', :action => 'tags'
+ map.tag 'tag/:tag', :controller => 'blog', :action => 'tag'
+ map.tag_feed 'tag/:tag.atom', :controller => 'blog', :action => 'tag', :format => 'atom'
+ map.article_comments 'articles/:article_id/comments',
+ :controller => 'blog', :action => 'create_comment',
+ :conditions => { :method => :post }
+
+ # map.stylesheet 'stylesheets/:action.:format', :controller => 'stylesheets'
+
+ map.connect 'admin', :controller => 'admin/panel', :action => 'index'
+ map.panel 'admin/panel', :controller => 'admin/panel', :action => 'index'
+ map.namespace :admin, :prefix => '' do |admin|
+ admin.resources :articles, :has_many => :comments
+ admin.resources :comments
+ end
+ map.login 'login', :controller => 'sess