Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added message posts model and a mess of supporting stuff

  • Loading branch information...
commit b7469b96bc4a9d29c785f7295074207882ec9ed9 1 parent a115f19
@jlapier authored
Showing with 1,591 additions and 122 deletions.
  1. +2 −0  app/controllers/forums_controller.rb
  2. +98 −0 app/controllers/message_posts_controller.rb
  3. +48 −0 app/helpers/gravatar_helper.rb
  4. +11 −0 app/helpers/message_posts_helper.rb
  5. +4 −1 app/models/forum.rb
  6. +63 −0 app/models/message_post.rb
  7. +7 −2 app/models/user.rb
  8. +80 −0 app/views/forums/_textile_ref.html.erb
  9. +8 −63 app/views/forums/index.html.erb
  10. +6 −27 app/views/forums/new.html.erb
  11. +16 −22 app/views/forums/show.html.erb
  12. +1 −0  app/views/layouts/application.html.erb
  13. +21 −0 app/views/message_posts/_message_form.html.erb
  14. +24 −0 app/views/message_posts/_message_in_forum.html.erb
  15. +19 −0 app/views/message_posts/_whole_message.html.erb
  16. +3 −0  app/views/message_posts/edit.html.erb
  17. +34 −0 app/views/message_posts/index.html.erb
  18. +3 −0  app/views/message_posts/new.html.erb
  19. +32 −0 app/views/message_posts/show.html.erb
  20. +3 −0  config/environment.rb
  21. +2 −0  config/routes.rb
  22. +27 −0 db/migrate/20090908180148_create_message_posts.rb
  23. +70 −2 public/stylesheets/application.css
  24. +44 −0 public/stylesheets/text_and_colors.css
  25. +131 −0 spec/controllers/message_posts_controller_spec.rb
  26. +1 −1  spec/fixtures/forums.yml
  27. +39 −0 spec/fixtures/message_posts.yml
  28. +1 −2  spec/fixtures/users.yml
  29. +11 −0 spec/helpers/message_posts_helper_spec.rb
  30. +4 −0 spec/integration/message_posts_spec.rb
  31. +20 −0 spec/models/message_post_spec.rb
  32. +63 −0 spec/routing/message_posts_routing_spec.rb
  33. +32 −0 spec/views/message_posts/edit.html.erb_spec.rb
  34. +39 −0 spec/views/message_posts/index.html.erb_spec.rb
  35. +32 −0 spec/views/message_posts/new.html.erb_spec.rb
  36. +27 −0 spec/views/message_posts/show.html.erb_spec.rb
  37. BIN  test.db
  38. +1 −2  test/fixtures/users.yml
  39. +20 −0 vendor/plugins/searchable_by/MIT-LICENSE
  40. +55 −0 vendor/plugins/searchable_by/README
  41. +23 −0 vendor/plugins/searchable_by/Rakefile
  42. +2 −0  vendor/plugins/searchable_by/init.rb
  43. +1 −0  vendor/plugins/searchable_by/install.rb
  44. +137 −0 vendor/plugins/searchable_by/lib/searchable_by.rb
  45. +4 −0 vendor/plugins/searchable_by/tasks/searchable_by_tasks.rake
  46. +21 −0 vendor/plugins/searchable_by/test/boot.rb
  47. +22 −0 vendor/plugins/searchable_by/test/database.yml
  48. +10 −0 vendor/plugins/searchable_by/test/fixtures/companies.yml
  49. +5 −0 vendor/plugins/searchable_by/test/fixtures/company.rb
  50. +5 −0 vendor/plugins/searchable_by/test/fixtures/employee.rb
  51. +28 −0 vendor/plugins/searchable_by/test/fixtures/employees.yml
  52. +18 −0 vendor/plugins/searchable_by/test/fixtures/schema.rb
  53. +12 −0 vendor/plugins/searchable_by/test/helper.rb
  54. +43 −0 vendor/plugins/searchable_by/test/lib/activerecord_test_case.rb
  55. +75 −0 vendor/plugins/searchable_by/test/lib/activerecord_test_connector.rb
  56. +9 −0 vendor/plugins/searchable_by/test/lib/load_fixtures.rb
  57. +73 −0 vendor/plugins/searchable_by/test/searchable_by_test.rb
  58. +1 −0  vendor/plugins/searchable_by/uninstall.rb
View
2  app/controllers/forums_controller.rb
@@ -16,6 +16,8 @@ def index
# GET /forums/1
# GET /forums/1.xml
def show
+ @message_posts = @forum.message_posts.paginate :page => params[:page], :order => 'created_at DESC'
+ @new_message_post = MessagePost.new :forum => @forum
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @forum }
View
98 app/controllers/message_posts_controller.rb
@@ -0,0 +1,98 @@
+class MessagePostsController < ApplicationController
+ before_filter :require_user, :except => [:index, :show]
+
+ # GET /message_posts
+ # GET /message_posts.xml
+ def index
+ @message_posts = MessagePost.all
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @message_posts }
+ end
+ end
+
+ # GET /message_posts/1
+ # GET /message_posts/1.xml
+ def show
+ @message_post = MessagePost.find(params[:id])
+ if @message_post.thread
+ redirect_to message_post_url(@message_post.thread, :anchor => @message_post.id)
+ else
+ @child_posts = @message_post.child_posts.paginate :page => params[:page], :order => 'created_at ASC'
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @message_post }
+ end
+ end
+ end
+
+ # GET /message_posts/new
+ # GET /message_posts/new.xml
+ def new
+ @message_post = MessagePost.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @message_post }
+ end
+ end
+
+ # GET /message_posts/1/edit
+ def edit
+ @message_post = MessagePost.find(params[:id])
+ end
+
+ # POST /message_posts
+ # POST /message_posts.xml
+ def create
+ @message_post = MessagePost.new(params[:message_post])
+ @message_post.user = current_user
+ respond_to do |format|
+ if @message_post.save
+ flash[:notice] = "Posted: #{@message_post.subject}"
+ format.html do
+ if @message_post.thread
+ redirect_to message_post_url(@message_post.thread, :anchor => @message_post.id,
+ :page => @message_post.thread.child_posts.last_page_number_for)
+ else
+ redirect_to(@message_post)
+ end
+ end
+ format.xml { render :xml => @message_post, :status => :created, :location => @message_post }
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @message_post.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /message_posts/1
+ # PUT /message_posts/1.xml
+ def update
+ @message_post = MessagePost.find(params[:id])
+
+ respond_to do |format|
+ if @message_post.update_attributes(params[:message_post])
+ flash[:notice] = 'MessagePost was successfully updated.'
+ format.html { redirect_to(@message_post) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @message_post.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /message_posts/1
+ # DELETE /message_posts/1.xml
+ def destroy
+ @message_post = MessagePost.find(params[:id])
+ @message_post.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(message_posts_url) }
+ format.xml { head :ok }
+ end
+ end
+end
View
48 app/helpers/gravatar_helper.rb
@@ -0,0 +1,48 @@
+require 'digest/md5'
+require 'cgi'
+
+module GravatarHelper
+
+ DEFAULT_OPTIONS = {
+ :default => "http://aethora.com/images/hunting.gif",
+ :size => 50,
+ :rating => 'PG',
+ :alt => 'avatar',
+ :class => 'gravatar',
+ }
+
+
+
+ # Return the HTML img tag for the given user's gravatar. Presumes that
+ # the given user object will respond_to "email", and return the user's
+ # email address.
+ def gravatar_for(user, options={})
+ gravatar(user.email, options)
+ end
+
+ # Return the HTML img tag for the given email address's gravatar.
+ def gravatar(email, options={})
+ src = h(gravatar_url(email, options))
+ options = DEFAULT_OPTIONS.merge(options)
+ [:class, :alt, :size].each { |opt| options[opt] = h(options[opt]) }
+ "<img class=\"#{options[:class]}\" alt=\"#{options[:alt]}\" width=\"#{options[:size]}\" "+
+ "height=\"#{options[:size]}\" src=\"#{src}\" />"
+ end
+
+ # Return the gravatar URL for the given email address.
+ def gravatar_url(email, options={})
+ email_hash = Digest::MD5.hexdigest(email)
+ options = DEFAULT_OPTIONS.merge(options)
+ options[:default] = CGI::escape(options[:default]) unless options[:default].nil?
+ returning "http://www.gravatar.com/avatar/#{email_hash}.jpg?" do |url|
+ [:rating, :size, :default].each do |opt|
+ unless options[opt].nil?
+ value = h(options[opt])
+ url << "#{opt}=#{value}&"
+ end
+ end
+ end
+ end
+
+
+end
View
11 app/helpers/message_posts_helper.rb
@@ -0,0 +1,11 @@
+module MessagePostsHelper
+ # TODO: change this to use the zoned plugin or something
+ def post_time(time)
+ if (Time.now - time) > 2600000
+ time.strftime "on %b %d, %Y"
+ else
+ time_ago_in_words(time) + " ago"
+ end
+ end
+
+end
View
5 app/models/forum.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20090904211126
+# Schema version: 20090908180148
#
# Table name: forums
#
@@ -15,4 +15,7 @@
class Forum < ActiveRecord::Base
validates_presence_of :title
+
+ belongs_to :most_recent_post, :class_name => 'MessagePost', :foreign_key => :newest_message_post_id
+ has_many :message_posts, :dependent => :destroy
end
View
63 app/models/message_post.rb
@@ -0,0 +1,63 @@
+# == Schema Information
+# Schema version: 20090908180148
+#
+# Table name: message_posts
+#
+# id :integer not null, primary key
+# subject :string(255)
+# body :text(16777215
+# forum_id :integer
+# parent_id :integer
+# user_id :integer
+# to_user_id :integer
+# thread_id :integer
+# replied_to_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# End Schema
+
+class MessagePost < ActiveRecord::Base
+ belongs_to :forum
+ belongs_to :user
+ belongs_to :thread, :class_name => 'MessagePost', :foreign_key => 'thread_id', :include => :user
+
+ has_many :child_posts, :class_name => 'MessagePost', :foreign_key => 'thread_id', :include => :user
+
+ validates_presence_of :subject, :body, :user_id
+
+ searchable_by :subject, :body
+
+ before_validation :fix_blank_subject
+
+ class << self
+ def search_forums(term)
+ # uses search from "searchable_by"
+ search term, nil, :limit => 20, :order => "messages.updated_at DESC"
+ end
+
+ def per_page
+ 10
+ end
+
+ def last_page_number_for(conditions=nil)
+ total = count :all, :conditions => conditions
+ [((total - 1) / per_page) + 1, 1].max
+ end
+ end
+
+ # only for threads
+ def most_recent_reply
+ @most_recent_reply ||= child_posts.find :first, :order => "message_posts.created_at DESC",
+ :include => :user
+ end
+
+ def fix_blank_subject
+ if subject.blank?
+ if thread
+ self.subject = "RE: #{thread.subject}"
+ else
+ self.subject = nil
+ end
+ end
+ end
+end
View
9 app/models/user.rb
@@ -2,7 +2,7 @@
# == Schema Information
-# Schema version: 20090904211126
+# Schema version: 20090908180148
#
# Table name: users
#
@@ -23,12 +23,12 @@
# last_login_ip :string(255)
# current_login_ip :string(255)
# is_admin :boolean
-# is_moderator :boolean
# End Schema
class User < ActiveRecord::Base
attr_protected :is_admin
attr_protected :is_moderator
+ has_many :message_posts
acts_as_authentic
@@ -36,4 +36,9 @@ class User < ActiveRecord::Base
def is_moderator_for_forum?(forum)
false
end
+
+
+ def name
+ display_name.blank? ? login : display_name
+ end
end
View
80 app/views/forums/_textile_ref.html.erb
@@ -0,0 +1,80 @@
+<h4>Textile Quick Reference</h4>
+
+<p>
+ <b>Phrase</b> modifiers:<br />
+ _<em>emphasis</em>_<br />
+ *<strong>strong</strong>*<br />
+ ??<cite>citation</cite>??<br />
+ -deleted text-<br />
+ +inserted text+<br />
+ ^superscript^<br />
+ ~subscript~<br />
+</p>
+
+<p>
+ To insert a <b>link</b>:<br />
+ "linktext":url<br />
+</p>
+
+<p>
+ To insert an <b>image</b>:<br />
+ !imageurl!<br />
+ !imageurl!:url<br />
+</p>
+
+<p>
+ <b>Block</b> modifiers:<br />
+ Header: <strong>h<em>n</em>.</strong><br />
+ Blockquote: <strong>bq.</strong><br />
+ Numeric list: <strong>#</strong><br />
+ Bulleted list: <strong>*</strong><br />
+</p>
+
+<div class="textile_advanced" style="display:none">
+
+ <p>
+ To apply <b>attributes:</b><br />
+ (class)<br />
+ (#id)<br />
+ {style}<br />
+ [language]<br />
+ </p>
+
+ <p>
+ To insert a <b>footnote</b>:<br />
+ See foo<strong>[<em>n</em>]</strong>.<br />
+ <strong>fn<em>n</em></strong>. Foo.<br />
+ </p>
+
+ <p>
+ To <b>align</b> blocks:<br />
+ <b>&#60;</b> right<br />
+ <b>&#62;</b> left<br /><b>=</b> center<br />
+ <b>&#60;&#62;</b> justify<br />
+ </p>
+
+ <p>
+ To insert a <b>table</b>:<br />
+ |a|table|row|<br />
+ |a|table|row|<br />
+ </p>
+
+ <p>
+ To define an <b>acronym</b>:<br />
+ ABC(Always Be Closing)<br />
+ </p>
+
+ <p>
+ <b>Punctuation</b>:<br>
+ <b>"</b>quotes<b>"</b> &rarr; &#8220;quotes&#8221;<br />
+ <b>'</b>quotes<b>'</b> &rarr; &#8216;quotes&#8217;<br />
+ it<b>'</b>s &rarr; it&#8217;s<br />
+ -&gt; &rarr; &rarr;<br />
+ em <b>--</b> dash</b> &rarr; em &#8212; dash<br />
+ en <b>-</b> dash &rarr; en &#8211; dash<br />
+ 2 <b>x</b> 4 &rarr; 2 &#215; 4<br />
+ foo<b>(tm)</b> &rarr; foo&#8482;<br />
+ foo<b>(r)</b> &rarr; foo&#174;<br />
+ foo<b>(c)</b> &rarr; foo&#169;<br />
+ </p>
+</div>
View
71 app/views/forums/index.html.erb
@@ -1,7 +1,7 @@
<h1>Forums</h1>
<div style="float:right">
- <% form_tag({ :controller => "/messages", :action => :seek }, :method => :get ) do %>
+ <% form_tag({ :controller => "/message_posts", :action => :search }, :method => :get ) do %>
<%= text_field_tag :q, "quick search", :onfocus => "$('q').value='';" %>
<%= submit_tag "Go" %>
<% end %>
@@ -17,7 +17,7 @@
<% for forum in @forums -%>
<tr>
<td style="border-bottom: 2px solid #DDE;">
- <p><strong><%= link_to forum.title, :action => :forum, :forum_id => forum %></strong></p>
+ <p><strong><%= link_to forum.title, forum %></strong></p>
<span class="small_text"><%= forum.description %></span>
</td>
<td style="text-align: right; border-bottom: 2px solid #DDE; width:225px">
@@ -36,75 +36,20 @@
</p>
</td>
</tr>
- <% end -%>
-
- <% if logged_in? -%>
+ <% if current_user and current_user.is_admin? %>
<tr>
- <td style="border-bottom: 2px solid #DDE;">
- <p><strong><%= link_to "My Log", :action => :log %></strong></p>
- <span class="small_text">Your own personal log, where you can scribe about whatever you want.</span>
- </td>
- <td style="text-align: right; border-bottom: 2px solid #DDE;">
- <p class="small_text" style="color: #666;">Most recent post:
- <% if current_user.newest_log_message -%>
- <%= link_to current_user.newest_log_message.subject, :action => :thread, :thread_id => current_user.newest_log_message, :mark_read => 1 %><br/>
- <%= "by #{current_user.newest_log_message.user.name} posted #{post_time current_user.newest_log_message.created_at}" %>
- <% else -%>
- <em>no messages</em>
- <% end -%>
- </p>
- <em style="color: #C60;">Cost to post: free</em>
+ <td class="small_text" colspan="2">
+ <%= link_to 'edit details of this forum', edit_forum_path(forum) %> |
+ <%= link_to 'delete this forum', forum, :confirm => 'Are you sure you want to delete this forum and all the messages in it?', :method => :delete %>
</td>
</tr>
+ <% end %>
<% end -%>
- <tr>
- <td style="border-bottom: 2px solid #DDE;">
- <p><strong><%= link_to "Other Logs", :action => :all_logs %></strong></p>
- <span class="small_text">Other personal logs - read what other players are writing about.</span>
- </td>
- <td style="text-align: right; border-bottom: 2px solid #DDE;">
- <p class="small_text" style="color: #666;">Most recent post:
- <% most_recent = Message.find_recent_log_posts(1).first %>
- <%= link_to most_recent.subject, :action => :thread, :thread_id => most_recent %><br/>
- <%= "by #{most_recent.user.login} posted #{post_time most_recent.created_at}" %>
- </p>
- <em style="color: #C60;">Cost to post: free</em>
- </td>
- </tr>
-
</table>
-
-
-
-<h1>Listing forums</h1>
-
-<table>
- <tr>
- <th>Title</th>
- <th>Description</th>
- <th>Position</th>
- <th>Moderator only</th>
- <th>Newest message post</th>
- </tr>
-
-<% @forums.each do |forum| %>
- <tr>
- <td><%=h forum.title %></td>
- <td><%=h forum.description %></td>
- <td><%=h forum.position %></td>
- <td><%=h forum.moderator_only %></td>
- <td><%=h forum.newest_message_post_id %></td>
- <td><%= link_to 'Show', forum %></td>
- <td><%= link_to 'Edit', edit_forum_path(forum) %></td>
- <td><%= link_to 'Destroy', forum, :confirm => 'Are you sure?', :method => :delete %></td>
- </tr>
-<% end %>
-</table>
-
<br />
-<% if current_user.is_admin? %>
+<% if current_user and current_user.is_admin? %>
<%= link_to 'create a new forum', new_forum_path %>
<% end %>
View
33 app/views/forums/new.html.erb
@@ -2,30 +2,9 @@
<% form_for(@forum) do |f| %>
<%= f.error_messages %>
-
- <p>
- <%= f.label :title %><br />
- <%= f.text_field :title %>
- </p>
- <p>
- <%= f.label :description %><br />
- <%= f.text_area :description %>
- </p>
- <p>
- <%= f.label :position %><br />
- <%= f.text_field :position %>
- </p>
- <p>
- <%= f.label :moderator_only %><br />
- <%= f.check_box :moderator_only %>
- </p>
- <p>
- <%= f.label :newest_message_post_id %><br />
- <%= f.text_field :newest_message_post_id %>
- </p>
- <p>
- <%= f.submit 'Create' %>
- </p>
-<% end %>
-
-<%= link_to 'Back', forums_path %>
+ Title of forum: <%= f.text_field :title %><br/>
+ Moderator only? <%= f.check_box :moderator_only %><br/>
+ Description:
+ <%= f.text_area :description, :rows => 5, :cols => 80 %><br/>
+ <%= submit_tag "Make this Forum!" %>
+<% end -%>
View
38 app/views/forums/show.html.erb
@@ -1,28 +1,22 @@
-<p>
- <b>Title:</b>
- <%=h @forum.title %>
-</p>
+<h1><%= link_to "Forums", forums_path %>: <%= @forum.title %></h1>
-<p>
- <b>Description:</b>
- <%=h @forum.description %>
-</p>
+<p class="small_text"><%= @forum.description %></p>
-<p>
- <b>Position:</b>
- <%=h @forum.position %>
-</p>
+<div style="text-align:center;"><%= will_paginate @message_posts %></div>
-<p>
- <b>Moderator only:</b>
- <%=h @forum.moderator_only %>
-</p>
+<table class="messages" cellspacing="0px" cellpadding="0px">
+ <% for message in @message_posts -%>
+ <%= render :partial => "message_posts/message_in_forum", :locals => { :message => message } %>
+ <% end -%>
+</table>
-<p>
- <b>Newest message post:</b>
- <%=h @forum.newest_message_post_id %>
-</p>
+<div style="text-align:center;"><%= will_paginate @message_posts %></div>
+<br/>
+
+<% unless @forum.moderator_only? and (current_user && !current_user.is_admin?) -%>
+ <h4>Start a new discussion thread in this forum:</h4>
+
+ <%= render :partial => "/message_posts/message_form", :locals => { :message_post => @new_message_post } %>
+<% end -%>
-<%= link_to 'Edit', edit_forum_path(@forum) %> |
-<%= link_to 'Back', forums_path %>
View
1  app/views/layouts/application.html.erb
@@ -5,6 +5,7 @@
<title><%= ["A Forum", @page_title].reject(&:blank?).join(' - ') %></title>
<%= stylesheet_link_tag 'reset' %>
<%= stylesheet_link_tag 'error_messages' %>
+ <%= stylesheet_link_tag 'text_and_colors' %>
<%= stylesheet_link_tag 'application' %>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<%= javascript_include_tag 'jquery.min' %>
View
21 app/views/message_posts/_message_form.html.erb
@@ -0,0 +1,21 @@
+<% if current_user -%>
+ <div id="message_box" style="float:left">
+ <% form_for message_post do |f| %>
+ <%= f.error_messages %>
+ <%= f.hidden_field(:forum_id) %>
+ <%= f.hidden_field(:thread_id) %>
+ <p>Subject: <%= f.text_field :subject %></p>
+ <p><%= f.text_area :body, :cols => 60, :rows => 10 %></p>
+ <p><%= submit_tag "Post it!" %></p>
+ <% end %>
+ </div>
+
+ <div style="float:right; width: 200px;" class="small_text">
+ <%= render :partial => '/forums/textile_ref' %>
+ </div>
+
+<% else -%>
+ <p><em>If you were <%= link_to "logged in", login_path %> you could post.</em></p>
+<% end -%>
+
+<div style="clear:both"></div>
View
24 app/views/message_posts/_message_in_forum.html.erb
@@ -0,0 +1,24 @@
+<tr>
+ <td class="leftside">
+ <div style="float:left; padding: 2px 4px;">
+ <%= gravatar_for message.user, :size => 50 %>
+ </div>
+ posted <%= post_time message.created_at %> by
+ <strong><%= link_to message.user.name, '#TODO_link_to_users_profile' %></strong>
+ </td>
+ <td><strong><%= link_to message.subject, message %>:</strong>
+ <span class="gray"><%=h truncate( message.body, 200 ) %></span>
+ </td>
+ <td class="message_last_reply">
+ Last Reply:
+ <% if message.most_recent_reply %>
+ <%= link_to message.most_recent_reply.subject, message,
+ :page => MessagePost.last_page_number_for(:thread_id => message),
+ :anchor => "bottom" %> <br/>
+ <em><%= message.most_recent_reply.user.name %></em>
+ <em><%= post_time message.most_recent_reply.created_at %></em>
+ <% else -%>
+ <em>no replies</em>
+ <% end -%>
+ </td>
+</tr>
View
19 app/views/message_posts/_whole_message.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td class="leftside">
+ posted <%= post_time message.created_at %> by
+ <strong><%= link_to message.user.name, '#TODO_link_to_users_profile' %></strong>
+ <div style="padding: 2px 4px;">
+ <%= gravatar_for message.user, :size => 80 %>
+ </div>
+ </td>
+ <td>
+ <div style="float:left"><a name="<%= message.id %>" href="#<%= message.id %>"><strong><%= message.subject %></strong></a></div>
+ <% if current_user == message.user -%>
+ <div style="float:right">[<%= link_to "edit", :action => :edit, :id => message %>]</div>
+ <% end -%>
+ <div style="clear:both; padding-top:3px;"><%= textilize message.body %></div>
+ <% if message.updated_at > message.created_at -%>
+ <div><em>Last Edit: <%= post_time message.updated_at %></em></div>
+ <% end -%>
+ </td>
+</tr>
View
3  app/views/message_posts/edit.html.erb
@@ -0,0 +1,3 @@
+<h1>Editing post <em><%= @message_post.subject %></em></h1>
+
+<%= render :partial => 'message_form', :locals => { :message_post => @message_post } %>
View
34 app/views/message_posts/index.html.erb
@@ -0,0 +1,34 @@
+<h1>Listing message_posts</h1>
+
+<table>
+ <tr>
+ <th>Subject</th>
+ <th>Body</th>
+ <th>Forum</th>
+ <th>Parent</th>
+ <th>User</th>
+ <th>To user</th>
+ <th>Thread</th>
+ <th>Replied to at</th>
+ </tr>
+
+<% @message_posts.each do |message_post| %>
+ <tr>
+ <td><%=h message_post.subject %></td>
+ <td><%=h message_post.body %></td>
+ <td><%=h message_post.forum_id %></td>
+ <td><%=h message_post.parent_id %></td>
+ <td><%=h message_post.user_id %></td>
+ <td><%=h message_post.to_user_id %></td>
+ <td><%=h message_post.thread_id %></td>
+ <td><%=h message_post.replied_to_at %></td>
+ <td><%= link_to 'Show', message_post %></td>
+ <td><%= link_to 'Edit', edit_message_post_path(message_post) %></td>
+ <td><%= link_to 'Destroy', message_post, :confirm => 'Are you sure?', :method => :delete %></td>
+ </tr>
+<% end %>
+</table>
+
+<br />
+
+<%= link_to 'New message_post', new_message_post_path %>
View
3  app/views/message_posts/new.html.erb
@@ -0,0 +1,3 @@
+<h1>Creating new post</h1>
+
+<%= render :partial => 'message_form', :locals => { :message_post => @message_post } %>
View
32 app/views/message_posts/show.html.erb
@@ -0,0 +1,32 @@
+<h1>
+ <% if @message_post.forum %>
+ <%= link_to "Forums", forums_path %>:
+ <%= link_to @message_post.forum.title, @message_post.forum %>
+ <% else %>
+ Message
+ <% end -%>
+</h1>
+
+<table class="messages" style="margin-bottom:10px;">
+ <%= render :partial => "whole_message", :locals => { :message => @message_post } %>
+</table>
+
+<div style="text-align:center;"><%= will_paginate @child_posts %></div>
+
+<table class="messages">
+ <% for message_post in @child_posts -%>
+ <%= render :partial => "whole_message", :locals => { :message => message_post } %>
+ <% end -%>
+</table>
+
+<a name="bottom"></a>
+
+<div style="text-align:center;"><%= will_paginate @child_posts %></div>
+
+<br/>
+
+<h3>Reply to this thread:</h3>
+<%= render :partial => "message_form",
+ :locals => { :message_post => MessagePost.new(:thread => @message_post, :subject => "RE: #{@message_post.subject}") } %>
+
+
View
3  config/environment.rb
@@ -25,11 +25,14 @@
# config.gem "bj"
# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "aws-s3", :lib => "aws/s3"
+ config.gem "RedCloth"
config.gem "authlogic"
config.gem "authlogic-oid", :lib => "authlogic_openid"
config.gem "ruby-openid", :lib => "openid"
config.gem "rspec", :lib => false, :version => ">= 1.2.0"
config.gem "rspec-rails", :lib => false, :version => ">= 1.2.0"
+ config.gem 'mislav-will_paginate', :version => '~> 2.3.11', :lib => 'will_paginate',
+ :source => 'http://gems.github.com'
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
View
2  config/routes.rb
@@ -1,4 +1,6 @@
ActionController::Routing::Routes.draw do |map|
+ map.resources :message_posts, :collection => { :search => :get }
+
map.resources :forums
map.resource :account, :controller => "users"
View
27 db/migrate/20090908180148_create_message_posts.rb
@@ -0,0 +1,27 @@
+class CreateMessagePosts < ActiveRecord::Migration
+ def self.up
+ create_table :message_posts do |t|
+ t.string :subject
+ t.text :body, :limit => 16777215
+ t.integer :forum_id
+ t.integer :parent_id
+ t.integer :user_id
+ t.integer :to_user_id
+ t.integer :thread_id
+ t.datetime :replied_to_at
+
+ t.timestamps
+ end
+
+ add_index :message_posts, ["created_at"], :name => "index_messages_on_created_at"
+ add_index :message_posts, ["forum_id"], :name => "index_messages_on_forum_id"
+ add_index :message_posts, ["parent_id"], :name => "index_messages_on_parent_id"
+ add_index :message_posts, ["thread_id"], :name => "index_messages_on_thread_id"
+ add_index :message_posts, ["to_user_id"], :name => "index_messages_on_to_user_id"
+ add_index :message_posts, ["user_id"], :name => "index_messages_on_user_id"
+ end
+
+ def self.down
+ drop_table :message_posts
+ end
+end
View
72 public/stylesheets/application.css
@@ -1,11 +1,11 @@
body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
- font-size: 12px;
line-height: 150%;
}
body {
- background-color: #84be7d;
+ background-color: #805c4f;
+ font-size: 12px;
}
p { padding: 8px 0px; }
@@ -25,6 +25,8 @@ ul { }
li { margin-left: 20px; }
+textarea { font-family: Arial, sans-serif; font-size: 12px; vertical-align:top; }
+
#top_banner {
text-align:center; height: 100px;
}
@@ -120,3 +122,69 @@ input#user_openid_identifier, input#user_session_openid_identifier {
.link_box a:link, .link_box a:visited { color: #009; }
.link_box a:hover { color: #00F; }
+
+
+
+/* forum stuff */
+
+
+table.forums { background: #DDE; width: 100%; border-spacing: 0px; }
+table.forums tr { background: #F6F6FF; }
+table.forums td { padding: 10px; font-size: 12px; }
+table.forums strong { font-size: 18px; font-weight: bold; }
+table.forums strong a {border: none; }
+table.forums td.small_text { font-size: 10px; }
+
+table.messages { background: #DDE; min-width: 600px; border-spacing: 4px; border-collapse: separate; }
+table.messages tr { background: #F6F6FF; border-spacing: 4px; }
+table.messages td { padding: 10px; font-size: 11px; }
+table.messages li { font-size: 11px; }
+table.messages strong { font-size: 12px; font-weight: bold; }
+table.messages td small_text { font-size: 10px; }
+table.messages blockquote { background: white; color: #333; border-left: 2px solid #999; padding-left: 6px; }
+table.messages tr td.leftside { text-align: right; vertical-align: top; border-left: 3px solid #AAA;
+ background: #DDE; font-size: 11px; width: 120px;
+}
+table.messages tr:hover td.leftside { border-left: 3px solid #009; }
+table.messages .lightgreen { color: #090; }
+
+.tag_list form { font-size: 9px; }
+.tag_list input { font-size: 10px; }
+#message_tag_list { font-size: 9px; }
+#tags_for_user { float: right; font-size: 10px; min-width: 50px;
+ padding: 10px; margin: 11px; border: 1px solid #009; background: #EEF;
+ text-align: right;
+}
+
+
+
+/* pagination */
+
+
+.pagination {
+ padding: 3px;
+ margin: 3px;
+}
+.pagination a {
+ padding: 2px 5px 2px 5px;
+ margin: 2px;
+ border: 1px solid #aaaadd;
+ text-decoration: none;
+ color: #000099;
+ background-color: #FFF;
+}
+.pagination a:hover, .pagination a:active {
+ border: 1px solid #000099;
+ color: #000;
+}
+.pagination span.current {
+ padding: 2px 5px 2px 5px;
+ margin: 2px;
+ border: 1px solid #000099;
+ font-weight: bold;
+ background-color: #000099;
+ color: #FFF;
+}
+.pagination span.disabled {
+ display:none;
+}
View
44 public/stylesheets/text_and_colors.css
@@ -0,0 +1,44 @@
+
+.red { color: red }
+.blue { color: blue }
+.green { color: green }
+.purple { color: #9F009C }
+.gold { color: #FFD700 }
+.yellow { color: #FD3; }
+.brown { color: #630; }
+.white { color: #FFF; }
+.lightbrown { color: #FC9; }
+.lightblue { color: #CCF; }
+.lightgreen { color: #90EE90; }
+.lightred { color: #F66; }
+.lightpurple { color: #9F339C }
+.lightgray { color: #aaa }
+.gray { color: #999 }
+.darkred { color: #900; }
+.darkblue { color: #009; }
+.darkgreen { color: #090; }
+.smalltext { font-size: 11px; }
+.small_text { font-size: 11px; }
+.big_text { font-size: 16px; line-height: 150% }
+.large_text { font-size: 18px; line-height: 150% }
+.huge_text { font-size: 22px; line-height: 150% }
+.tiny_text { font-size: 9px; }
+.pink_background { background: #FDD; }
+.aqua_background { background: #DFF; }
+div.smalltext ul { font-size: 11px; }
+
+table.nice_table { background: #660; }
+table.nice_table th { background: #CC9; padding: 2px; font-size: 12px;}
+table.nice_table tr { background: #FFF; }
+table.nice_table td { padding: 4px; font-size: 11px;}
+
+table.nice_data_table { background: #660; }
+table.nice_data_table th { background: #CC9; padding: 4px 8px; font-size: 13px;}
+table.nice_data_table tr { background: #FFF; }
+table.nice_data_table td { padding: 4px; font-size: 12px; text-align:right; }
+
+
+table.small_table td { padding: 3px; margin: 1px; font-size: 11px; }
+table.small_table_narrow td { padding: 2px; margin: 0px; font-size: 11px; }
+table.tiny_table td { padding: 0px 2px; margin: 0px 2px; font-size: 9px; }
+
View
131 spec/controllers/message_posts_controller_spec.rb
@@ -0,0 +1,131 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe MessagePostsController do
+
+ def mock_message_post(stubs={})
+ @mock_message_post ||= mock_model(MessagePost, stubs)
+ end
+
+ describe "GET index" do
+ it "assigns all message_posts as @message_posts" do
+ MessagePost.stub!(:find).with(:all).and_return([mock_message_post])
+ get :index
+ assigns[:message_posts].should == [mock_message_post]
+ end
+ end
+
+ describe "GET show" do
+ it "assigns the requested message_post as @message_post" do
+ MessagePost.stub!(:find).with("37").and_return(mock_message_post)
+ get :show, :id => "37"
+ assigns[:message_post].should equal(mock_message_post)
+ end
+ end
+
+ describe "GET new" do
+ it "assigns a new message_post as @message_post" do
+ MessagePost.stub!(:new).and_return(mock_message_post)
+ get :new
+ assigns[:message_post].should equal(mock_message_post)
+ end
+ end
+
+ describe "GET edit" do
+ it "assigns the requested message_post as @message_post" do
+ MessagePost.stub!(:find).with("37").and_return(mock_message_post)
+ get :edit, :id => "37"
+ assigns[:message_post].should equal(mock_message_post)
+ end
+ end
+
+ describe "POST create" do
+
+ describe "with valid params" do
+ it "assigns a newly created message_post as @message_post" do
+ MessagePost.stub!(:new).with({'these' => 'params'}).and_return(mock_message_post(:save => true))
+ post :create, :message_post => {:these => 'params'}
+ assigns[:message_post].should equal(mock_message_post)
+ end
+
+ it "redirects to the created message_post" do
+ MessagePost.stub!(:new).and_return(mock_message_post(:save => true))
+ post :create, :message_post => {}
+ response.should redirect_to(message_post_url(mock_message_post))
+ end
+ end
+
+ describe "with invalid params" do
+ it "assigns a newly created but unsaved message_post as @message_post" do
+ MessagePost.stub!(:new).with({'these' => 'params'}).and_return(mock_message_post(:save => false))
+ post :create, :message_post => {:these => 'params'}
+ assigns[:message_post].should equal(mock_message_post)
+ end
+
+ it "re-renders the 'new' template" do
+ MessagePost.stub!(:new).and_return(mock_message_post(:save => false))
+ post :create, :message_post => {}
+ response.should render_template('new')
+ end
+ end
+
+ end
+
+ describe "PUT update" do
+
+ describe "with valid params" do
+ it "updates the requested message_post" do
+ MessagePost.should_receive(:find).with("37").and_return(mock_message_post)
+ mock_message_post.should_receive(:update_attributes).with({'these' => 'params'})
+ put :update, :id => "37", :message_post => {:these => 'params'}
+ end
+
+ it "assigns the requested message_post as @message_post" do
+ MessagePost.stub!(:find).and_return(mock_message_post(:update_attributes => true))
+ put :update, :id => "1"
+ assigns[:message_post].should equal(mock_message_post)
+ end
+
+ it "redirects to the message_post" do
+ MessagePost.stub!(:find).and_return(mock_message_post(:update_attributes => true))
+ put :update, :id => "1"
+ response.should redirect_to(message_post_url(mock_message_post))
+ end
+ end
+
+ describe "with invalid params" do
+ it "updates the requested message_post" do
+ MessagePost.should_receive(:find).with("37").and_return(mock_message_post)
+ mock_message_post.should_receive(:update_attributes).with({'these' => 'params'})
+ put :update, :id => "37", :message_post => {:these => 'params'}
+ end
+
+ it "assigns the message_post as @message_post" do
+ MessagePost.stub!(:find).and_return(mock_message_post(:update_attributes => false))
+ put :update, :id => "1"
+ assigns[:message_post].should equal(mock_message_post)
+ end
+
+ it "re-renders the 'edit' template" do
+ MessagePost.stub!(:find).and_return(mock_message_post(:update_attributes => false))
+ put :update, :id => "1"
+ response.should render_template('edit')
+ end
+ end
+
+ end
+
+ describe "DELETE destroy" do
+ it "destroys the requested message_post" do
+ MessagePost.should_receive(:find).with("37").and_return(mock_message_post)
+ mock_message_post.should_receive(:destroy)
+ delete :destroy, :id => "37"
+ end
+
+ it "redirects to the message_posts list" do
+ MessagePost.stub!(:find).and_return(mock_message_post(:destroy => true))
+ delete :destroy, :id => "1"
+ response.should redirect_to(message_posts_url)
+ end
+ end
+
+end
View
2  spec/fixtures/forums.yml
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20090904211126
+# Schema version: 20090908180148
#
# Table name: forums
#
View
39 spec/fixtures/message_posts.yml
@@ -0,0 +1,39 @@
+# Read about fixtures at http://ar.rubyonrails.org/# == Schema Information
+# Schema version: 20090908180148
+#
+# Table name: message_posts
+#
+# id :integer not null, primary key
+# subject :string(255)
+# body :text(16777215
+# forum_id :integer
+# parent_id :integer
+# user_id :integer
+# to_user_id :integer
+# thread_id :integer
+# replied_to_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# End Schema
+
+classes/Fixtures.html
+
+one:
+ subject: MyString
+ body: MyText
+ forum_id: 1
+ parent_id: 1
+ user_id: 1
+ to_user_id: 1
+ thread_id: 1
+ replied_to_at: 2009-09-08 11:01:48
+
+two:
+ subject: MyString
+ body: MyText
+ forum_id: 1
+ parent_id: 1
+ user_id: 1
+ to_user_id: 1
+ thread_id: 1
+ replied_to_at: 2009-09-08 11:01:48
View
3  spec/fixtures/users.yml
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20090904211126
+# Schema version: 20090908180148
#
# Table name: users
#
@@ -20,6 +20,5 @@
# last_login_ip :string(255)
# current_login_ip :string(255)
# is_admin :boolean
-# is_moderator :boolean
# End Schema
View
11 spec/helpers/message_posts_helper_spec.rb
@@ -0,0 +1,11 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe MessagePostsHelper do
+
+ #Delete this example and add some real ones or delete this file
+ it "is included in the helper object" do
+ included_modules = (class << helper; self; end).send :included_modules
+ included_modules.should include(MessagePostsHelper)
+ end
+
+end
View
4 spec/integration/message_posts_spec.rb
@@ -0,0 +1,4 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe "MessagePosts" do
+end
View
20 spec/models/message_post_spec.rb
@@ -0,0 +1,20 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe MessagePost do
+ before(:each) do
+ @valid_attributes = {
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1,
+ :replied_to_at => Time.now
+ }
+ end
+
+ it "should create a new instance given valid attributes" do
+ MessagePost.create!(@valid_attributes)
+ end
+end
View
63 spec/routing/message_posts_routing_spec.rb
@@ -0,0 +1,63 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe MessagePostsController do
+ describe "route generation" do
+ it "maps #index" do
+ route_for(:controller => "message_posts", :action => "index").should == "/message_posts"
+ end
+
+ it "maps #new" do
+ route_for(:controller => "message_posts", :action => "new").should == "/message_posts/new"
+ end
+
+ it "maps #show" do
+ route_for(:controller => "message_posts", :action => "show", :id => "1").should == "/message_posts/1"
+ end
+
+ it "maps #edit" do
+ route_for(:controller => "message_posts", :action => "edit", :id => "1").should == "/message_posts/1/edit"
+ end
+
+ it "maps #create" do
+ route_for(:controller => "message_posts", :action => "create").should == {:path => "/message_posts", :method => :post}
+ end
+
+ it "maps #update" do
+ route_for(:controller => "message_posts", :action => "update", :id => "1").should == {:path =>"/message_posts/1", :method => :put}
+ end
+
+ it "maps #destroy" do
+ route_for(:controller => "message_posts", :action => "destroy", :id => "1").should == {:path =>"/message_posts/1", :method => :delete}
+ end
+ end
+
+ describe "route recognition" do
+ it "generates params for #index" do
+ params_from(:get, "/message_posts").should == {:controller => "message_posts", :action => "index"}
+ end
+
+ it "generates params for #new" do
+ params_from(:get, "/message_posts/new").should == {:controller => "message_posts", :action => "new"}
+ end
+
+ it "generates params for #create" do
+ params_from(:post, "/message_posts").should == {:controller => "message_posts", :action => "create"}
+ end
+
+ it "generates params for #show" do
+ params_from(:get, "/message_posts/1").should == {:controller => "message_posts", :action => "show", :id => "1"}
+ end
+
+ it "generates params for #edit" do
+ params_from(:get, "/message_posts/1/edit").should == {:controller => "message_posts", :action => "edit", :id => "1"}
+ end
+
+ it "generates params for #update" do
+ params_from(:put, "/message_posts/1").should == {:controller => "message_posts", :action => "update", :id => "1"}
+ end
+
+ it "generates params for #destroy" do
+ params_from(:delete, "/message_posts/1").should == {:controller => "message_posts", :action => "destroy", :id => "1"}
+ end
+ end
+end
View
32 spec/views/message_posts/edit.html.erb_spec.rb
@@ -0,0 +1,32 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "/message_posts/edit.html.erb" do
+ include MessagePostsHelper
+
+ before(:each) do
+ assigns[:message_post] = @message_post = stub_model(MessagePost,
+ :new_record? => false,
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1
+ )
+ end
+
+ it "renders the edit message_post form" do
+ render
+
+ response.should have_tag("form[action=#{message_post_path(@message_post)}][method=post]") do
+ with_tag('input#message_post_subject[name=?]', "message_post[subject]")
+ with_tag('textarea#message_post_body[name=?]', "message_post[body]")
+ with_tag('input#message_post_forum_id[name=?]', "message_post[forum_id]")
+ with_tag('input#message_post_parent_id[name=?]', "message_post[parent_id]")
+ with_tag('input#message_post_user_id[name=?]', "message_post[user_id]")
+ with_tag('input#message_post_to_user_id[name=?]', "message_post[to_user_id]")
+ with_tag('input#message_post_thread_id[name=?]', "message_post[thread_id]")
+ end
+ end
+end
View
39 spec/views/message_posts/index.html.erb_spec.rb
@@ -0,0 +1,39 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "/message_posts/index.html.erb" do
+ include MessagePostsHelper
+
+ before(:each) do
+ assigns[:message_posts] = [
+ stub_model(MessagePost,
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1
+ ),
+ stub_model(MessagePost,
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1
+ )
+ ]
+ end
+
+ it "renders a list of message_posts" do
+ render
+ response.should have_tag("tr>td", "value for subject".to_s, 2)
+ response.should have_tag("tr>td", "value for body".to_s, 2)
+ response.should have_tag("tr>td", 1.to_s, 2)
+ response.should have_tag("tr>td", 1.to_s, 2)
+ response.should have_tag("tr>td", 1.to_s, 2)
+ response.should have_tag("tr>td", 1.to_s, 2)
+ response.should have_tag("tr>td", 1.to_s, 2)
+ end
+end
View
32 spec/views/message_posts/new.html.erb_spec.rb
@@ -0,0 +1,32 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "/message_posts/new.html.erb" do
+ include MessagePostsHelper
+
+ before(:each) do
+ assigns[:message_post] = stub_model(MessagePost,
+ :new_record? => true,
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1
+ )
+ end
+
+ it "renders new message_post form" do
+ render
+
+ response.should have_tag("form[action=?][method=post]", message_posts_path) do
+ with_tag("input#message_post_subject[name=?]", "message_post[subject]")
+ with_tag("textarea#message_post_body[name=?]", "message_post[body]")
+ with_tag("input#message_post_forum_id[name=?]", "message_post[forum_id]")
+ with_tag("input#message_post_parent_id[name=?]", "message_post[parent_id]")
+ with_tag("input#message_post_user_id[name=?]", "message_post[user_id]")
+ with_tag("input#message_post_to_user_id[name=?]", "message_post[to_user_id]")
+ with_tag("input#message_post_thread_id[name=?]", "message_post[thread_id]")
+ end
+ end
+end
View
27 spec/views/message_posts/show.html.erb_spec.rb
@@ -0,0 +1,27 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "/message_posts/show.html.erb" do
+ include MessagePostsHelper
+ before(:each) do
+ assigns[:message_post] = @message_post = stub_model(MessagePost,
+ :subject => "value for subject",
+ :body => "value for body",
+ :forum_id => 1,
+ :parent_id => 1,
+ :user_id => 1,
+ :to_user_id => 1,
+ :thread_id => 1
+ )
+ end
+
+ it "renders attributes in <p>" do
+ render
+ response.should have_text(/value\ for\ subject/)
+ response.should have_text(/value\ for\ body/)
+ response.should have_text(/1/)
+ response.should have_text(/1/)
+ response.should have_text(/1/)
+ response.should have_text(/1/)
+ response.should have_text(/1/)
+ end
+end
View
BIN  test.db
Binary file not shown
View
3  test/fixtures/users.yml
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20090904211126
+# Schema version: 20090908180148
#
# Table name: users
#
@@ -20,6 +20,5 @@
# last_login_ip :string(255)
# current_login_ip :string(255)
# is_admin :boolean
-# is_moderator :boolean
# End Schema
View
20 vendor/plugins/searchable_by/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 [name of plugin creator]
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
55 vendor/plugins/searchable_by/README
@@ -0,0 +1,55 @@
+SearchableBy
+============
+
+Adds a search method to query your ActiveRecord models:
+
+searchable_by :field_a, :field_b, :field_etc
+
+YourModel.search 'some search words'
+
+Examples
+========
+
+# declare with searchable_by
+class User < ActiveRecord::Base
+ enables the "search" method and by default searches 'login' and 'email' columns
+ searchable_by :login, :email
+end
+
+# search into associations
+class User < ActiveRecord::Base
+ has_many :addresses
+ searchable_by :login, :email, :addresses => [:street, :city]
+end
+
+# simple search - based on default fields in searchable_by line
+User.search 'fred'
+=> [#<User>, #<User>]
+
+# specify which fields to search on with :narrow_fields
+User.search 'fred', :narrow_fields => [:email, :login, :name]
+=> [#<User>, #<User>, #<User>]
+
+# use default search fields but pass other options
+User.search 'fred', :conditions => { :is_admin => true }, :limit => 2
+=> [#<User>, #<User>]
+
+# use :page option and we'll assume that you want to paginate using will_paginate
+User.search 'j', :page => 2
+=> [#<User>, #<User>, #<User>, #<User>, #<User>]
+
+# by default, all search words must be found
+User.search 'fred john jack susan'
+=> []
+
+# set :require_all to false if you want to do an "OR" search
+User.search 'fred john jack susan', :require_all => false
+=> [#<User>, #<User>, #<User>, #<User>, #<User>]
+
+# use double quotes to identify phrases
+User.search '"fred flintstone"', :narrow_fields => [:login, :full_name]
+=> [#<User>]
+
+
+
+Copyright (c) 2008-2009 Jason LaPier, released under the MIT license
View
23 vendor/plugins/searchable_by/Rakefile
@@ -0,0 +1,23 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the searchable_by plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the searchable_by plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'SearchableBy'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
View
2  vendor/plugins/searchable_by/init.rb
@@ -0,0 +1,2 @@
+require 'searchable_by'
+ActiveRecord::Base.send(:include, Offtheline::SearchableBy)
View
1  vendor/plugins/searchable_by/install.rb
@@ -0,0 +1 @@
+# Install hook code here
View
137 vendor/plugins/searchable_by/lib/searchable_by.rb
@@ -0,0 +1,137 @@
+# Adds a search method to query your ActiveRecord models
+#
+# # declare with searchable_by
+# class User < ActiveRecord::Base
+# # enables the "search" method and by default searches 'login' and 'email' columns
+# searchable_by :login, :email
+# end
+#
+# # search into associations
+# class User < ActiveRecord::Base
+# has_many :addresses
+# searchable_by :user => [:login, :email], :addresses => [:street, :city]
+# end
+#
+# simple search - based on default fields in searchable_by line
+# User.search 'fred'
+# => [#<User>, #<User>]
+#
+# # specify which fields to search on with :narrow_fields
+# User.search 'fred', :narrow_fields => [:email, :login, :name]
+# => [#<User>, #<User>, #<User>]
+#
+# # use default search fields but pass other options
+# User.search 'fred', :conditions => { :is_admin => true }, :limit => 2
+# => [#<User>, #<User>]
+#
+# # use :page option and we'll assume that you want to paginate using will_paginate
+# User.search 'j', :page => 2
+# => [#<User>, #<User>, #<User>, #<User>, #<User>]
+#
+# # by default, all search words must be found
+# User.search 'fred john jack susan'
+# => []
+#
+# # set :require_all to false if you want to do an "OR" search
+# User.search 'fred john jack susan', :require_all => false
+# => [#<User>, #<User>, #<User>, #<User>, #<User>]
+#
+# # use double quotes to identify phrases
+# User.search '"fred flintstone"', :narrow_fields => [:login, :full_name]
+# => [#<User>]
+#
+
+module Offtheline
+ module SearchableBy
+ def self.included(klass)
+ klass.extend ClassMethods
+ end
+
+ module ClassMethods
+ def searchable_by(*tables_and_columns)
+ @search_tables_and_columns = make_hash_from_cols_and_tables(tables_and_columns)
+ end
+
+ def search_tables_and_columns
+ @search_tables_and_columns
+ end
+
+ # in addition to other find options, there is an option to
+ # :require_all keywords or phrases (true by default) and an option
+ # :narrow_fields to change the fields you want to search from the default specified in searchable_by
+ def search(query, options = {})
+ cols_with_tables = options[:narrow_fields] ? make_hash_from_cols_and_tables(options[:narrow_fields]) : @search_tables_and_columns
+ options[:require_all] = true if options[:require_all].nil?
+
+ with_scope :find => { :conditions => search_conditions(query, options[:require_all], cols_with_tables),
+ :include => cols_with_tables.keys.reject{|k| k == class_name.tableize.to_sym } } do
+ # strip out 'search' specific options
+ search_options = options.reject { |k,v| [:require_all, :narrow_fields].include? k.to_sym }
+ if options.include?(:page)
+ # I think this will help us fail if will_paginate is not installed
+ gem 'mislav-will_paginate'
+ # take out require_all option when calling paginate
+ paginate :all, search_options
+ else
+ # take out require_all option when calling find
+ find :all, search_options
+ end
+ end
+ end
+
+ private
+
+ def make_hash_from_cols_and_tables(tables_and_columns)
+ tables_and_columns = [tables_and_columns].flatten
+ hash_of_tables_and_columns = { class_name.tableize.to_sym => [] }
+ tables_and_columns.each do |table_or_column|
+ if table_or_column.is_a? Hash
+ hash_of_tables_and_columns.merge! table_or_column
+ else
+ hash_of_tables_and_columns[class_name.tableize.to_sym] << table_or_column
+ end
+ end
+ hash_of_tables_and_columns
+ end
+
+ def search_conditions(query, require_all, cols_with_tables=nil )
+ return nil if query.blank?
+ cols_with_tables ||= @search_tables_and_columns
+
+ # pull out "quoted phrases" - this regular expression will find all matches of phrases
+ # that start and end with a double-quote
+ phrases = query.scan(/\"\w+[^\"]*\w+\"/)
+ unless phrases.empty?
+ # replace the phrase with an empty string
+ phrases.each { |phrase| query.gsub! phrase, '' }
+ # take the quotes out of the phrase
+ phrases = phrases.map { |ph| ph.gsub("\"", '') }
+ end
+
+ # note how we split keywords by commas and spaces, just in case
+ words = query.split(",").map(&:split).flatten
+
+ # the uniq takes care of any dupes
+ words_and_phrases = (words + phrases).uniq
+
+ binds = {} # bind symbols
+ or_frags = [] # OR fragments
+ cnt = 1 # to keep count on the symbols and OR fragments
+
+ # create 'conditions' parts for each word
+ for word_or_phrase in words_and_phrases
+ # generate our little SQL bites for each field that we want to check for the keyword
+ like_frags = cols_with_tables.map { |table,fields|
+ [fields].flatten.map { |f| "#{table.to_s.tableize}.#{f} LIKE :word#{cnt}" } }.flatten
+ or_frags << "(#{like_frags.join(" OR " )})"
+ binds["word#{cnt}".to_sym] = "%#{word_or_phrase.to_s.downcase}%"
+ cnt += 1
+ end
+
+ # return the OR fragments joined by ANDs (if require_all) or ORs
+ # and the named bind variables
+ [or_frags.join(require_all ? " AND " : " OR " ), binds]
+ end
+ end
+ end
+end
View
4 vendor/plugins/searchable_by/tasks/searchable_by_tasks.rake
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :searchable_by do
+# # Task goes here
+# end
View
21 vendor/plugins/searchable_by/test/boot.rb
@@ -0,0 +1,21 @@
+plugin_root = File.join(File.dirname(__FILE__), '..')
+version = ENV['RAILS_VERSION']
+version = nil if version and version == ""
+
+# first look for a symlink to a copy of the framework
+if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
+ puts "found framework root: #{framework_root}"
+ # this allows for a plugin to be tested outside of an app and without Rails gems
+ $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
+else
+ # simply use installed gems if available
+ puts "using Rails#{version ? ' ' + version : nil} gems"
+ require 'rubygems'
+
+ if version
+ gem 'rails', version
+ else
+ gem 'actionpack'
+ gem 'activerecord'
+ end
+end
View
22 vendor/plugins/searchable_by/test/database.yml
@@ -0,0 +1,22 @@
+sqlite3:
+ database: ":memory:"
+ adapter: sqlite3
+ timeout: 500
+
+sqlite2:
+ database: ":memory:"
+ adapter: sqlite2
+
+mysql:
+ adapter: mysql
+ username: root
+ password:
+ encoding: utf8
+ database: will_paginate_unittest
+
+postgres:
+ adapter: postgresql
+ username: mislav
+ password:
+ database: will_paginate_unittest
+ min_messages: warning
View
10 vendor/plugins/searchable_by/test/fixtures/companies.yml
@@ -0,0 +1,10 @@
+planex:
+ id: 1
+ name: Planet Express
+ abbrev: PlanEx
+ descriptors: Shipping, Delivery
+moms:
+ id: 2
+ name: "Mom's Friendly Robots"
+ abbrev: MomCorp
+ descriptors: Mom, Robots, World Domination
View
5 vendor/plugins/searchable_by/test/fixtures/company.rb
@@ -0,0 +1,5 @@
+class Company < ActiveRecord::Base
+ has_many :employees
+
+ searchable_by :name, :abbrev, :descriptors
+end
View
5 vendor/plugins/searchable_by/test/fixtures/employee.rb
@@ -0,0 +1,5 @@
+class Employee < ActiveRecord::Base
+ belongs_to :company
+
+ searchable_by :first_name, :last_name, :occupation, :company => [:name, :abbrev]
+end
View
28 vendor/plugins/searchable_by/test/fixtures/employees.yml
@@ -0,0 +1,28 @@
+fry:
+ first_name: Philip
+ last_name: Fry
+ occupation: Delivery Boy
+ company_id: 1
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
+leela:
+ first_name: Turanga
+ last_name: Leela
+ occupation: Captain
+ company_id: 1
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
+bender:
+ first_name: Bender
+ last_name: Rodriguez
+ occupation: Bending Unit
+ company_id: 1
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
+walt:
+ first_name: Walt
+ last_name: Unknown
+ occupation: Stooge
+ company_id: 2
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
View
18 vendor/plugins/searchable_by/test/fixtures/schema.rb
@@ -0,0 +1,18 @@
+ActiveRecord::Schema.define do
+
+ create_table "employees", :force => true do |t|
+ t.column "first_name", :string
+ t.column "last_name", :string
+ t.column "occupation", :string
+ t.column "company_id", :integer
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ end
+
+ create_table "companies", :force => true do |t|
+ t.column "name", :string
+ t.column "abbrev", :string
+ t.column "descriptors", :text
+ end
+
+end
View
12 vendor/plugins/searchable_by/test/helper.rb
@@ -0,0 +1,12 @@
+require 'test/unit'
+require 'rubygems'
+
+# gem install redgreen for colored test output
+begin require 'redgreen'; rescue LoadError; end
+
+require 'boot' unless defined?(ActiveRecord)
+
+require 'active_record'
+
+require "../lib/searchable_by"
+ActiveRecord::Base.send(:include, Offtheline::SearchableBy)
View
43 vendor/plugins/searchable_by/test/lib/activerecord_test_case.rb
@@ -0,0 +1,43 @@
+require 'lib/activerecord_test_connector'
+
+class ActiveRecordTestCase < Test::Unit::TestCase
+ if defined?(ActiveSupport::Testing::SetupAndTeardown)
+ include ActiveSupport::Testing::SetupAndTeardown
+ end
+
+ if defined?(ActiveRecord::TestFixtures)
+ include ActiveRecord::TestFixtures
+ end
+ # Set our fixture path
+ if ActiveRecordTestConnector.able_to_connect
+ self.fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures')
+ self.use_transactional_fixtures = true
+ end
+
+ def self.fixtures(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ def run(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ # Default so Test::Unit::TestCase doesn't complain
+ def test_truth
+ end
+
+ protected
+
+ def assert_queries(num = 1)
+ $query_count = 0
+ yield
+ ensure
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
+ end
+
+ def assert_no_queries(&block)
+ assert_queries(0, &block)
+ end
+end
+
+ActiveRecordTestConnector.setup
View
75 vendor/plugins/searchable_by/test/lib/activerecord_test_connector.rb
@@ -0,0 +1,75 @@
+require 'active_record'
+require 'active_record/version'
+require 'active_record/fixtures'
+
+class ActiveRecordTestConnector
+ cattr_accessor :able_to_connect
+ cattr_accessor :connected
+
+ FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures')
+
+ # Set our defaults
+ self.connected = false
+ self.able_to_connect = true
+
+ def self.setup
+ unless self.connected || !self.able_to_connect
+ setup_connection
+ load_schema
+ add_load_path FIXTURES_PATH
+ self.connected = true
+ end
+ rescue Exception => e # errors from ActiveRecord setup
+ $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n"
+ self.able_to_connect = false
+ end
+
+ private
+
+ def self.add_load_path(path)
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
+ dep.load_paths.unshift path
+ end
+
+ def self.setup_connection
+ db = ENV['DB'].blank?? 'sqlite3' : ENV['DB']
+
+ configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'database.yml'))
+ raise "no configuration for '#{db}'" unless configurations.key? db
+ configuration = configurations[db]
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
+ puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank?
+
+ gem 'sqlite3-ruby' if 'sqlite3' == db
+
+ ActiveRecord::Base.establish_connection(configuration)
+ ActiveRecord::Base.configurations = { db => configuration }
+ prepare ActiveRecord::Base.connection
+
+ unless Object.const_defined?(:QUOTED_TYPE)
+ Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
+ end
+ end
+
+ def self.load_schema
+ ActiveRecord::Base.silence do
+ ActiveRecord::Migration.verbose = false
+ load File.join(FIXTURES_PATH, 'schema.rb')
+ end
+ end
+
+ def self.prepare(conn)
+ class << conn
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /]
+
+ def execute_with_counting(sql, name = nil, &block)
+ $query_count ||= 0
+ $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
+ execute_without_counting(sql, name, &block)
+ end
+
+ alias_method_chain :execute, :counting
+ end
+ end
+end
View
9 vendor/plugins/searchable_by/test/lib/load_fixtures.rb
@@ -0,0 +1,9 @@
+require 'boot'
+require 'lib/activerecord_test_connector'
+
+# setup the connection
+ActiveRecordTestConnector.setup
+
+# load all fixtures
+Fixtures.create_fixtures(ActiveRecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables)
+
View
73 vendor/plugins/searchable_by/test/searchable_by_test.rb
@@ -0,0 +1,73 @@
+# NOTE: This test has to be run from the same directory this file (searchable_by_test.rb) is in.
+
+require "helper"
+require 'lib/activerecord_test_case'
+
+require '../lib/searchable_by'
+
+class SearchableByTest < ActiveRecordTestCase
+ fixtures :companies, :employees
+
+ def test_new_methods_presence
+ assert Employee.respond_to?('search')
+ assert Employee.respond_to?('search_tables_and_columns')
+ assert_equal( { :employees => [:first_name, :last_name, :occupation], :company => [:name, :abbrev] },
+ Employee.search_tables_and_columns)
+ end
+
+ def test_basic_search
+ results_for_one = Employee.search "Bender"
+ assert_equal 1, results_for_one.size
+ assert_equal "Bender", results_for_one.first.first_name
+ results_for_none = Employee.search "Flexo"
+ assert_equal 0, results_for_none.size
+ results_for_one_company = Company.search "Mom"
+ assert_equal 1, results_for_one_company.size
+ end
+
+ def test_associative_search
+ results_for_three = Employee.search "Planet Express"
+ assert_equal 3, results_for_three.size
+ assert results_for_three.map(&:first_name).include?("Bender")
+ assert !results_for_three.map(&:first_name).include?("Walt")
+ results_for_one = Employee.search "MomCorp"
+ assert_equal 1, results_for_one.size
+ assert_equal "Walt", results_for_one.first.first_name
+ end
+
+ def test_quoted_search
+ results_for_one = Employee.search '"Delivery Boy"'
+ assert_equal 1, results_for_one.size
+ assert_equal "Philip", results_for_one.first.first_name
+ results_for_none = Employee.search '"Boy Delivery"'
+ assert_equal 0, results_for_none.size
+ end
+
+ def test_no_require_all_search
+ results_for_none = Employee.search 'Delivery Unit'
+ assert_equal 0, results_for_none.size
+ results_for_two = Employee.search 'Delivery Unit', :require_all => false
+ assert_equal 2, results_for_two.size
+ assert results_for_two.map(&:first_name).include?("Bender")
+ assert results_for_two.map(&:first_name).include?("Philip")
+ end
+
+ def test_specify_field_search
+ results_for_one = Employee.search 'Delivery', :narrow_fields => :occupation
+ assert_equal 1, results_for_one.size
+ assert_equal 'Philip', results_for_one.first.first_name
+ results_for_none = Employee.search 'Philip', :narrow_fields => :occupation
+ assert_equal 0, results_for_none.size
+ results_for_three = Employee.search 'Planet', :narrow_fields => { :company => :name }
+ assert_equal 3, results_for_three.size
+ assert results_for_three.map(&:first_name).include?("Bender")
+ assert !results_for_three.map(&:first_name).include?("Walt")
+ end
+
+ def test_use_normal_find_options
+ results_for_three = Employee.search 'PlanEx', :order => "last_name DESC"
+ assert_equal 3, results_for_three.size
+ assert_equal ['Rodriguez', 'Leela', 'Fry'], results_for_three.map(&:last_name)
+ end
+
+end
View
1  vendor/plugins/searchable_by/uninstall.rb
@@ -0,0 +1 @@
+# Uninstall hook code here
Please sign in to comment.
Something went wrong with that request. Please try again.