Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Ui #52

Closed
wants to merge 6 commits into from

2 participants

This page is out of date. Refresh to see the latest.
View
2  app/controllers/motions_controller.rb
@@ -24,7 +24,7 @@ def show
# Show more records for the motion state section
def show_more
- @motions = Motion.where(:state_name => params[:state]).where('id < ?', params[:id]).order('created_at DESC').limit(6)
+ @motions = Motion.prev_with_same_state(params[:id]).order('created_at DESC').limit(6)
end
# Create a new Event
View
6 app/helpers/motions_helper.rb
@@ -29,7 +29,11 @@ def render_motion_list(motions, &block)
def link_to_more_motions(motions)
if motions.count > motions.size
- render(:partial => 'motions/link_to_more', :locals => { :state => motions.last.state_name, :last_id => motions.last.id })
+ content_tag(
+ :div,
+ link_to('More', show_more_motions_path, :class => 'more_motions quick-tool', :'data-last-id' => motions.last.id),
+ :class => 'more_motions'
+ )
end
end
end
View
2  app/models/motion.rb
@@ -18,6 +18,8 @@ class Motion < ActiveRecord::Base
scope :discussing, where(:state_name => 'discussing')
scope :voting, where(:state_name => 'voting')
+ scope :prev_with_same_state, lambda { |id| where('id < ?', id).where(:state_name => Motion.find(id).state_name) }
+
validates :state_name, :inclusion => { :in => MOTION_STATES }
belongs_to :member
View
22 app/stylesheets/application.scss
@@ -162,8 +162,11 @@ div.motion {
}
div.more_motions {
- width: 25px;
+ width: 100%;
+ overflow: hidden;
+ margin-top: 10px;
}
+
div.more_motions:hover {
text-decoration: underline;
cursor: pointer;
@@ -202,6 +205,7 @@ div.more_motions:hover {
}
h1 {
+ margin-bottom: 10px;
padding-top: 0;
font-size: 3em;
color: $header_green;
@@ -212,10 +216,24 @@ div.more_motions:hover {
.current-motions {
+ h2 {
+ margin-top: 18px;
+ padding: 4px 10px 1px;
+ font-size: 1.7em;
+ color: #333;
+ background-color: #f0f0f0;
+ }
+
+ div.empty {
+ padding: 10px;
+ border: 1px solid #f0f0f0;
+ font-size: 1.7em;
+ }
+
ul {
border: 1px solid #f0f0f0;
border-bottom: 0;
- margin: .5em 0 0;
+ margin: 0 0 5px;
}
li {
View
1  app/views/motions/_link_to_more.html.haml
@@ -1 +0,0 @@
-%div.more_motions{ :id => "motion_state_#{state}_link", :data => { :state => state, :"last-id" => last_id } } More
View
20 app/views/motions/index.html.haml
@@ -8,20 +8,20 @@
%h1
Current Motions
- %section
- %h2 Waiting For Seconds
- - render_motion_list(@motions.waitingsecond) do
- %p There are no motions available to review
-
- %section
- %h2 Discussing
- - render_motion_list(@motions.discussing) do
- %p There are no motions available to review
+ - if active_member?
+ %section
+ %h2 Waiting For Seconds
+ - render_motion_list(@motions.waitingsecond) do
+ .empty There are no motions available to review
+ %section
+ %h2 Discussing
+ - render_motion_list(@motions.discussing) do
+ .empty There are no motions available to review
%section
%h2 Voting
- render_motion_list(@motions.voting) do
- %p There are no motions available to review
+ .empty There are no motions available to review
=link_to 'See closed motions', closed_motions_path
View
2  doc/README_FOR_APP
@@ -1,2 +0,0 @@
-Use this README file to introduce your application and point to useful places in the API for learning more.
-Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
View
73 doc/motion_states.md
@@ -0,0 +1,73 @@
+Motion State System
+===================
+
+This is a low-level description of the motion state system. It will be much
+easier to understand if you follow along in the code, so I encourage you to do
+that.
+
+States
+------
+
+ * __Waiting for Seconds__ (`waitingsecond`) The initial state. Members must
+ second a motion in order for it to enter the `discussing` state (or
+ alternatively jump straight to the `voting` state).
+ * __Discussing__ (`discussing`) Unless the motion has been expedited and has
+ received 1/3 seconds, it enters the `discussing` state before the `voting`
+ state. In this state, members can discuss the motion before voting or
+ perhaps object to it.
+ * __In Voting__ (`voting`) In order for the motion to pass the majority of the
+ members must vote in favor of it.
+ * __Closed__ (`closed`) A closed motion is either approved or failed.
+
+Motion Creation
+---------------
+
+When a motion is initialized and saved three things happen:
+
+ 1. The `state_name` attribute is set to its default value, `waitingsecond`.
+ 2. The `after_initialize` callback fires the `assign_state` method, which
+ sets the `@state` instance variable to an instance of the appropriate
+ "state class" namespaced under the `MotionState` module. For example
+ `waitingsecond` maps to `MotionState::Waitingsecond`.
+ 3. The `after_create` callback triggers the `schedule_updates` method which
+ delegates to a method of the same name in the state instance, which will
+ schedule the `scheduled_update` method to be called in a given amount of
+ time.
+
+State Class Structure
+----------------------
+
+Most state classes have the following in common:
+
+ 1. They inherit from `MotionState::Base` which is a very simple class mostly
+ consisting of empty methods that state classes are expected to override.
+ 2. They include one or more of the modules nested under `MotionState`:
+ `PubliclyViewable`, `ActiveMemberViewable`, `NoSecondable`, and
+ `NoVotable`.
+ 3. They define a number of permission methods, i.e. methods that are named
+ like `permit_some_action?`.
+ 4. They define a `schedule_updates` method which basically schedules a call
+ to the `scheduled_update` method in a given amount of time.
+ 5. They define an `update` and a `scheduled_update` method.
+
+Permission Methods
+------------------
+
+What people are permitted to do to a motion, varies depending on its state.
+For example, only active members can see a motion when it's in the
+`waitingsecond` state. And no one can vote for a motion unless it's in the
+`voting` state. Currently, the system expects states to have three permission
+methods: `permit_see?`, `permit_second?`, and `permit_vote?`.
+
+Updates Vs. Scheduled Updates
+-----------------------------
+
+Looking at the diagram in the design document you can see that much of what is
+going on with a motion is in the lines of "change state, wait a specific amount
+of time, decide what then to do". For example, a motion is closed if it has
+failed to receive enough seconds to enter the `discussing` state within 48
+hours. That happens in a scheduled update.
+
+A regular update, on the other hand, happens when an event related to that
+motion occurs, e.g. someone seconds it. For example, a motion enters the
+`discussing` state as soon as it has received two seconds.
View
2  features/motions.feature → features/member_views_motion.feature
@@ -1,4 +1,4 @@
-Feature: Member views the Motions
+Feature: Member views motion
Scenario: I am an active member viewing the motions
Given I am signed in as an active member
View
2  features/public_browses_votes.feature
@@ -5,7 +5,7 @@ Feature: Public browses votes
Background:
Given I am an anonymous visitor
- And a motion titled "jQuery should rule the JS World" exists
+ And a motion titled "jQuery should rule the JS World" exists in the "voting" state
Scenario: A visitor sees the a list of motions
Given I am on the homepage
View
5 public/javascripts/application.js
@@ -2,13 +2,14 @@
// This file is automatically included by javascript_include_tag :defaults
$(document).ready(function(){
- $('.more_motions').live('click', function(){
+ $('a.more_motions').live('click', function(e){
var self = $(this);
- var data = {state: self.attr('data-state'), id: self.attr('data-last-id')};
+ var data = {id: self.attr('data-last-id')};
var ul = self.closest("section").find("ul");
self.remove();
$.get("/motions/show_more", data, function(html) {
$(ul).append(html);
});
+ e.preventDefault();
});
});
View
63 public/stylesheets/application.css
@@ -159,10 +159,12 @@ div.motion {
/* line 164, ../../app/stylesheets/application.scss */
div.more_motions {
- width: 25px;
+ width: 100%;
+ overflow: hidden;
+ margin-top: 10px;
}
-/* line 167, ../../app/stylesheets/application.scss */
+/* line 170, ../../app/stylesheets/application.scss */
div.more_motions:hover {
text-decoration: underline;
cursor: pointer;
@@ -170,7 +172,7 @@ div.more_motions:hover {
/* @end */
/* @group Homepage */
-/* line 179, ../../app/stylesheets/application.scss */
+/* line 182, ../../app/stylesheets/application.scss */
#content .current-motions a.quick-tool {
background: #229922;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #44bb44), color-stop(100%, #229922));
@@ -190,22 +192,23 @@ div.more_motions:hover {
font-size: 1.8em !important;
border: 1px solid #070;
}
-/* line 193, ../../app/stylesheets/application.scss */
+/* line 196, ../../app/stylesheets/application.scss */
#content .current-motions a.quick-tool:hover {
background: #3da83d;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #3da83d), color-stop(100%, #1d841d));
background-image: -moz-linear-gradient(top, #3da83d 0%, #1d841d 100%);
background-image: linear-gradient(top, #3da83d 0%, #1d841d 100%);
}
-/* line 198, ../../app/stylesheets/application.scss */
+/* line 201, ../../app/stylesheets/application.scss */
#content .current-motions a.quick-tool:active {
background: #57c257;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #57c257), color-stop(100%, #27ae27));
background-image: -moz-linear-gradient(top, #57c257 0%, #27ae27 100%);
background-image: linear-gradient(top, #57c257 0%, #27ae27 100%);
}
-/* line 204, ../../app/stylesheets/application.scss */
+/* line 207, ../../app/stylesheets/application.scss */
#content .current-motions h1 {
+ margin-bottom: 10px;
padding-top: 0;
font-size: 3em;
color: #407a27;
@@ -213,35 +216,49 @@ div.more_motions:hover {
font-style: italic;
}
-/* line 215, ../../app/stylesheets/application.scss */
+/* line 219, ../../app/stylesheets/application.scss */
+.current-motions h2 {
+ margin-top: 18px;
+ padding: 4px 10px 1px;
+ font-size: 1.7em;
+ color: #333;
+ background-color: #f0f0f0;
+}
+/* line 227, ../../app/stylesheets/application.scss */
+.current-motions div.empty {
+ padding: 10px;
+ border: 1px solid #f0f0f0;
+ font-size: 1.7em;
+}
+/* line 233, ../../app/stylesheets/application.scss */
.current-motions ul {
border: 1px solid #f0f0f0;
border-bottom: 0;
- margin: .5em 0 0;
+ margin: 0 0 5px;
}
-/* line 221, ../../app/stylesheets/application.scss */
+/* line 239, ../../app/stylesheets/application.scss */
.current-motions li {
padding: 1em;
border-bottom: 1px solid #f0f0f0;
}
-/* line 225, ../../app/stylesheets/application.scss */
+/* line 243, ../../app/stylesheets/application.scss */
.current-motions li.odd {
background: #f0fff0 !important;
}
-/* line 229, ../../app/stylesheets/application.scss */
+/* line 247, ../../app/stylesheets/application.scss */
.current-motions li.passed {
background-color: #90EE90 !important;
}
-/* line 233, ../../app/stylesheets/application.scss */
+/* line 251, ../../app/stylesheets/application.scss */
.current-motions li.failed {
background-color: #FAAFBE !important;
}
-/* line 237, ../../app/stylesheets/application.scss */
+/* line 255, ../../app/stylesheets/application.scss */
.current-motions li a {
font-size: 1.75em;
text-decoration: none;
}
-/* line 241, ../../app/stylesheets/application.scss */
+/* line 259, ../../app/stylesheets/application.scss */
.current-motions li a.plus {
float: right;
height: 18px;
@@ -252,12 +269,12 @@ div.more_motions:hover {
color: #000;
font-size: 1px;
}
-/* line 253, ../../app/stylesheets/application.scss */
+/* line 271, ../../app/stylesheets/application.scss */
.current-motions li span.meta {
font-size: 1.25em !important;
color: #80a080;
}
-/* line 258, ../../app/stylesheets/application.scss */
+/* line 276, ../../app/stylesheets/application.scss */
.current-motions li p {
color: #b8b8b8;
font-style: italic;
@@ -266,26 +283,26 @@ div.more_motions:hover {
margin: .25em 0 0;
}
-/* line 270, ../../app/stylesheets/application.scss */
+/* line 288, ../../app/stylesheets/application.scss */
#motion h1 {
color: #407a27;
font-size: 3em;
}
-/* line 275, ../../app/stylesheets/application.scss */
+/* line 293, ../../app/stylesheets/application.scss */
#motion ul {
float: right;
}
-/* line 280, ../../app/stylesheets/application.scss */
+/* line 298, ../../app/stylesheets/application.scss */
nav.main ul {
float: right;
}
-/* line 283, ../../app/stylesheets/application.scss */
+/* line 301, ../../app/stylesheets/application.scss */
nav.main ul li {
margin-top: 5px;
float: left;
}
-/* line 287, ../../app/stylesheets/application.scss */
+/* line 305, ../../app/stylesheets/application.scss */
nav.main ul li.first a {
-moz-border-radius-bottomleft: 6px;
-webkit-border-bottom-left-radius: 6px;
@@ -294,7 +311,7 @@ nav.main ul li.first a {
-khtml-border-bottom-left-radius: 6px;
border-bottom-left-radius: 6px;
}
-/* line 291, ../../app/stylesheets/application.scss */
+/* line 309, ../../app/stylesheets/application.scss */
nav.main ul li a {
font-size: 1.25em;
font-family: helvetica, arial, sans-serif;
@@ -312,7 +329,7 @@ nav.main ul li a {
cursor: pointer;
background: url("http://jquery.org/wp-content/themes/green/green/shadow.png") bottom left repeat-x;
}
-/* line 304, ../../app/stylesheets/application.scss */
+/* line 322, ../../app/stylesheets/application.scss */
nav.main ul li a:hover {
background-color: rgba(0, 0, 0, 0.1);
}
Something went wrong with that request. Please try again.