Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Discussions CRUD + Listing #22

Open
wants to merge 8 commits into from

2 participants

@practicingruby

Hi Jordan, we aren't ready to merge upstream yet, but I wanted to do an interim review request just so that we can learn a few things and roll in some of your suggestions before we do merge.

Jia and I have worked on making it so discussions can be associated with an author. To do this, we ended up having to do a bit of gravatar integration and refactoring of PersonDecorator, but we made those changes on master because they seemed to be generally useful.

After making those changes, we updated the code on our feature branch to implement authorship and gravatars, as shown by the screenshot below.

I left a few questions for you on one of our commits, but would be happy to hear any other feedback you have as well.

NOTE: In order to run this code it is necessary to update any discussions from our original seed data using something like:

Discussion.all.each { |a| a.update_attribute(:author, "sandal") }

We plan to add very simple discussion CRUD before merging, and delete the db/seeds.rb file. But I figured I'd let you know about this gotcha in the interim.

@practicingruby

Is using a partial the right thing to do here? I copied roughly what I saw you doing in practicing-ruby-web, but I've never used sass before myself.

@practicingruby

here I tried to use a discussion_list class to sort of namespace my changes, not sure if that's the right way to go. See my decorator for how I use this.

@practicingruby

We aren't sure whether having one decorator for Discussions covering both the collection and the individual records or having independent ones for the collection and individual records is preferred. I ended up going with separating them out, because it seems weird to me to have instance methods that can be called on both objects (an association or the record itself) but will only actually work on one target object.

Still, it seems awkward and perhaps Draper has a better way? It seems like a common problem to want to decorate both the collection and the individual record,.

@practicingruby

Related to my earlier question about Sass, is a class appropriate here?

@practicingruby

I assume this is how we're going to associate people in clubhouse to various records. I wonder though... it seems like using github nicknames all over the place is risky business (changing a nickname will break stuff!). Perhaps we should have clubhouse provide something like clubhouse_id for us to use?

@practicingruby

@jordanbyron: We are spiking on discussions CRUD right now because we want to get a feel for how that will affect the main listing page. Please don't bother reviewing until we clean things up a bit. Or if you want to give us some feedback, focus on the first commits related to integrating gravatar. Answering my questions on those will help me sort of whether we've been doing things right in our more recent commits or not.

@jordanbyron
Owner

@sandal no problem. Let me know when this is ready for me to check out :eyes:

@practicingruby

Hi Jordan,

Now that we've reached the end of the course, its time for us to wrap up this pull request, I suppose. What we have here is a good proof of concept (and likely a usable implementation) of the discussion listing page, and some VERY rough CRUD code for the creation and editing of new discussions. There are a number of limitations to this code to be aware of:

  • Currently we use an HTTP DELETE to archive posts. We have UI elements to close (archive) a discussion, but none to open them. There is no UI for deletion of discussions, because I haven't decided how that should work.

  • Currently there is no access control, all discussions can be created and edited by everyone

  • We did not make any progress at all on comments

  • We enabled HTML in discussion bodies as a proof of concept, but really we'll want to do Markdown instead here.

  • We have gravatars for people in discussions, but they do not currently link to anything (this depends on Chris + Vinicius's work)

  • The DiscussionListDecorator can probably be refactored quite a bit

  • There are no tests!

It's up to you how you want to go forward with this. I think it definitely would be possible to gradually refactor this code into what we will end up using for real, but it's not close to being in a usable state yet. In any case, additional testing would be essential as we move forward. Right now most of the interesting logic is in the decorators, but I haven't sorted out the best way to test them, especially considering that the structure might change quite a bit.

My rough idea for what might be the right approach is to get some feedback from you on this code, merge it quickly to prevent bitrot, and then make aggressive cuts to it as needed when we actually start working on this feature for real. During that time, we'd shore up the testing and make some decisions about the things that have been left undone so far. We could probably work on this together over the next couple weeks, if that sounds good to you.

Let us know what you think, and please give some feedback on what we built when you get a chance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2012
  1. @practicingruby

    Add authorship to discussions and basic gravatar URLS

    practicingruby authored Jia Brown committed
  2. added date for each discussion

    Jia Brown authored
Commits on Mar 27, 2012
  1. add new discussion link

    Jia Brown authored
  2. @practicingruby

    add rough discussion create screen

    practicingruby authored Jia Brown committed
  3. @practicingruby

    Continue to hack away at discussion crud, totally just spiking

    practicingruby authored Jia Brown committed
Commits on Mar 29, 2012
  1. @practicingruby

    Minor refactorings

    practicingruby authored
  2. @practicingruby
This page is out of date. Refresh to see the latest.
View
1  app/assets/stylesheets/application.css.sass
@@ -1,3 +1,4 @@
//= require_self
@import partials/base
+@import partials/discussions
View
18 app/assets/stylesheets/partials/_discussions.sass
@@ -0,0 +1,18 @@
+img.discussions
+ float: left
+ padding-right: 10px
+
+h3.discussions
+ padding-top: 5px
+ margin-bottom: 10px
+
+p.discussions.date
+ float: right
+ padding-top: 10px
+ margin-bottom: 10px
+
+a.discussions.new
+ float: right
+
+a.discussions.show
+ text-decoration: none
View
39 app/controllers/discussions_controller.rb
@@ -1,14 +1,51 @@
class DiscussionsController < ApplicationController
before_filter :find_course
+ before_filter :find_discussion, :except => [:new, :create, :index]
def index
- matches = Discussion.search(params)
+ matches = @course.discussions.search(params)
@discussions = DiscussionListDecorator.new(matches)
end
+ def new
+ @discussion = Discussion.new
+ # TODO: unify this with decorator logic
+ @category = params[:category] || "conversations"
+ end
+
+ def update
+ @discussion.update_attributes(params[:discussion])
+ redirect_to course_discussion_path(@course.id, @discussion)
+ end
+
+ def show
+ @discussion = DiscussionDecorator.decorate(@discussion)
+ end
+
+ def create
+ discussion_params = params[:discussion].
+ merge(:author => current_person.github_nickname,
+ :course_id => @course.id)
+
+ discussion = Discussion.create(discussion_params)
+ redirect_to course_discussion_path(@course.id, discussion)
+ end
+
+ # TODO: Seperate this into an admin feature which really destroys,
+ # and an archive route. Also, provide a way to re-open the discussions.
+ def destroy
+ @discussion.update_attribute(:archived, true)
+
+ redirect_to course_discussions_path
+ end
+
private
def find_course
@course = Course.find(params[:course_id])
end
+
+ def find_discussion
+ @discussion = Discussion.find(params[:id])
+ end
end
View
51 app/decorators/discussion_decorator.rb
@@ -0,0 +1,51 @@
+class DiscussionDecorator < ApplicationDecorator
+ decorates :discussion
+ allows :subject, :body
+
+ def author
+ PersonDecorator.from_github_name(discussion.author)
+ end
+
+ def start_date
+ discussion.created_at.strftime("%Y-%m-%d")
+ end
+
+ def header
+ h.content_tag(:p, start_date, :class => ["discussions","date"]) +
+ h.image_tag(author.gravatar_url(30), :class => "discussions") +
+ h.content_tag(:h2, discussion.subject)
+ end
+
+ def body
+ discussion.body.to_s.html_safe
+ end
+
+ def state
+ discussion.archived ? "closed" : "open"
+ end
+
+ # TODO: Support unarchive
+ def footer
+ [ edit_link, archive_link, discussion_list_link ].join(" | ").html_safe
+ end
+
+ private
+
+ def edit_link
+ path = h.edit_course_discussion_path(discussion.course.id, discussion.id)
+
+ h.link_to("Edit", path)
+ end
+
+ def archive_link
+ h.link_to_if(!discussion.archived, "Archive",
+ h.course_discussion_path(discussion.course.id, discussion.id),
+ :method => :delete, :confirm => "Are you sure?") { "Unarchive" }
+ end
+
+ def discussion_list_link
+ h.link_to("Back to #{discussion.category}",
+ h.course_discussions_path(:category => discussion.category,
+ :state => state))
+ end
+end
View
28 app/decorators/discussion_list_decorator.rb
@@ -1,5 +1,5 @@
class DiscussionListDecorator < ApplicationDecorator
- decorates :discussion
+ decorates :discussions, :class => Discussion
def category_filters
[conversations_link, reviews_link, evaluations_link].join(" | ").html_safe
@@ -23,15 +23,33 @@ def state_filters
end
def matched_discussions
- all.map do |discussion|
- h.content_tag(:h3, discussion.subject)
+ discussions.all.map do |discussion|
+ course = discussion.course
+ discussion = DiscussionDecorator.new(discussion)
+ h.image_tag(discussion.author.gravatar_url(30), :class => "discussions") +
+ h.content_tag(:p, discussion.start_date, :class => ["discussions","date"]) +
+ h.link_to(
+ h.content_tag(:h3, discussion.subject, :class => "discussions"),
+ h.course_discussion_path(course, discussion),
+ :class => ["discussions","show"]) +
+ h.content_tag(:hr)
end.join.html_safe
end
+
+ def new_discussion_link
+ h.link_to("New Discussion",
+ h.new_course_discussion_path(:category => category),
+ :class => ["discussions","new"])
+ end
+
+ def category
+ h.params[:category] || "conversations"
+ end
private
def discussions_link(category_name)
- if h.params[:category] == category_name
+ if category == category_name
category_name.capitalize
else
link_params = h.params.merge(:category => category_name)
@@ -40,8 +58,6 @@ def discussions_link(category_name)
end
def conversations_link
- return "Conversations" if h.params[:category].blank?
-
discussions_link("conversations")
end
View
1  app/models/discussion.rb
@@ -1,6 +1,7 @@
class Discussion < ActiveRecord::Base
VALID_CATEGORIES = ["conversations", "reviews", "evaluations"]
+ validates_presence_of :category
belongs_to :course
def self.search(params)
View
14 app/views/discussions/_form.html.haml
@@ -0,0 +1,14 @@
+= form_for(@discussion, :url => url) do |f|
+ %p
+ - if @discussion.new_record?
+ = f.hidden_field :category, :value => @category
+
+ = f.label :subject
+ %br
+ = f.text_field :subject, :value => @discussion.subject
+ %p
+ = f.label :body
+ %br
+ = f.text_area :body, :value => @discussion.body
+
+ = f.submit
View
4 app/views/discussions/edit.html.haml
@@ -0,0 +1,4 @@
+%h2 Edit your discussion
+
+= render :partial => "form",
+ :locals => { :url => course_discussion_path(@course.id, @discussion) }
View
1  app/views/discussions/index.html.haml
@@ -3,6 +3,7 @@
%br
%br
+= @discussions.new_discussion_link
= @discussions.state_filters
%br
View
3  app/views/discussions/new.html.haml
@@ -0,0 +1,3 @@
+%h2 Create a new discussion
+
+= render :partial => "form", :locals => { :url => course_discussions_path }
View
4 app/views/discussions/show.html.haml
@@ -0,0 +1,4 @@
+= @discussion.header
+= @discussion.body
+%hr
+= @discussion.footer
View
5 db/migrate/20120325235236_add_author_field_to_discussion.rb
@@ -0,0 +1,5 @@
+class AddAuthorFieldToDiscussion < ActiveRecord::Migration
+ def change
+ add_column :discussions, :author, :string
+ end
+end
View
3  db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20120317200349) do
+ActiveRecord::Schema.define(:version => 20120325235236) do
create_table "course_memberships", :force => true do |t|
t.integer "course_id"
@@ -36,6 +36,7 @@
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "archived", :default => false
+ t.string "author"
end
create_table "tasks", :force => true do |t|
View
30 db/seeds.rb
@@ -11,33 +11,3 @@
people.each do |person|
person.create(course_id: course.id, role: "Student") if person.empty?
end
-
-conversations = [ "How awesome is Jordan Byron?",
- "How many unicorns does it take to make a rainbow?",
- "Can someone ask a serious question please?" ]
-
-conversations.each do |e|
- course.discussions.find_or_create_by_subject(
- :subject => e, :category => "conversations"
- )
-end
-
-evaluations = [ "My s10-e2 is ready for evaluation",
- "Revisions have been made to my s10-e1",
- "Come on, evaluate it, you know you want to!" ]
-
-evaluations.each do |e|
- course.discussions.find_or_create_by_subject(
- :subject => e, :category => "evaluations"
- )
-end
-
-reviews = ["Messy first spike on my individual project",
- "Proof of concept patch to add archives to Newman",
- "If you don't review this soon, I might go crazy!"]
-
-reviews.each do |e|
- course.discussions.find_or_create_by_subject(
- :subject => e, :category => "reviews"
- )
-end
Something went wrong with that request. Please try again.