Skip to content

Instantly share code, notes, and snippets.

@TMorgan99
Created June 30, 2009 19:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TMorgan99/138341 to your computer and use it in GitHub Desktop.
Save TMorgan99/138341 to your computer and use it in GitHub Desktop.
# Initialize hobo
$ rm -fr agility
$ hobo agility
$ cd agility
{: .bash}
### add db:seeds rake task
{: .bash}
::: ./lib/tasks/database.rake
new file mode 100644
@@ -0,0 +1,15 @@
+# until rails 3.0, we grab this task from the edge...
+namespace :db do
+ desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
+ task :reset => [ 'db:drop', 'db:setup' ]
+
+ desc 'Create the database, load the schema, and initialize with the seed data'
+ task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ]
+
+ desc 'Load the seed data from db/seeds.rb'
+ task :seed => :environment do
+ seed_file = File.join(Rails.root, 'db', 'seeds.rb')
+ load(seed_file) if File.exist?(seed_file)
+ end
+end
+
{: .diff .ruby .dryml}
### add cucumber and rspec
$ ./script/generate rspec -q
$ ./script/generate cucumber -q
{: .bash}
# Generate the app, models and resources
$ ./script/generate hobo_model_resource project name:string
$ ./script/generate hobo_model_resource story title:string body:text status:string
$ ./script/generate hobo_model_resource task description:string
$ ./script/generate hobo_model task_assignment
{: .bash}
### Associations
{: .bash}
::: ./app/models/project.rb
@@ -7,6 +7,7 @@ class Project < ActiveRecord::Base
timestamps
end
+ has_many :stories, :dependent => :destroy
# --- Permissions --- #
{: .diff .ruby .dryml}
::: ./app/models/story.rb
@@ -9,6 +9,9 @@ class Story < ActiveRecord::Base
timestamps
end
+ belongs_to :project
+
+ has_many :tasks, :dependent => :destroy
# --- Permissions --- #
{: .diff .ruby .dryml}
::: ./app/models/task.rb
@@ -7,6 +7,10 @@ class Task < ActiveRecord::Base
timestamps
end
+ belongs_to :story
+
+ has_many :task_assignments, :dependent => :destroy
+ has_many :users, :through => :task_assignments
# --- Permissions --- #
{: .diff .ruby .dryml}
::: ./app/models/task_assignment.rb
@@ -6,6 +6,8 @@ class TaskAssignment < ActiveRecord::Base
timestamps
end
+ belongs_to :user
+ belongs_to :task
# --- Permissions --- #
{: .diff .ruby .dryml}
::: ./app/models/user.rb
@@ -9,6 +9,9 @@ class User < ActiveRecord::Base
timestamps
end
+ has_many :task_assignments, :dependent => :destroy
+ has_many :tasks, :through => :task_assignments
+
# This gives admin rights to the first sign-up.
# Just remove it if you don't want that
before_create { |user| user.administrator = true if RAILS_ENV != "test" && count == 0 }
{: .diff .ruby .dryml}
### Run the Hobo Migrator
$ ./script/generate hobo_migration initial_models --default-name --migrate
{: .bash}
## Removing Actions / Permissions
{: .bash}
::: ./app/controllers/stories_controller.rb
@@ -2,6 +2,8 @@ class StoriesController < ApplicationController
hobo_model_controller
- auto_actions :all
+ auto_actions :all, :except => :index
+
+ auto_actions_for :project, [:new, :create]
end
{: .diff .ruby .dryml}
::: ./app/controllers/tasks_controller.rb
@@ -2,6 +2,8 @@ class TasksController < ApplicationController
hobo_model_controller
- auto_actions :all
+ auto_actions :write_only, :edit
+
+ auto_actions_for :story, :create
end
{: .diff .ruby .dryml}
::: ./app/models/story.rb
@@ -20,7 +20,7 @@ class Story < ActiveRecord::Base
end
def update_permitted?
- acting_user.administrator?
+ acting_user.signed_up? && !project_changed?
end
def destroy_permitted?
{: .diff .ruby .dryml}
::: ./app/models/task.rb
@@ -10,7 +10,7 @@ class Task < ActiveRecord::Base
belongs_to :story
has_many :task_assignments, :dependent => :destroy
- has_many :users, :through => :task_assignments
+ has_many :users, :through => :task_assignments, :accessible => true
# --- Permissions --- #
@@ -19,7 +19,7 @@ class Task < ActiveRecord::Base
end
def update_permitted?
- acting_user.administrator?
+ acting_user.signed_up? && !story_changed?
end
def destroy_permitted?
{: .diff .ruby .dryml}
## Customising views
{: .bash}
::: ./app/controllers/projects_controller.rb
@@ -4,4 +4,11 @@ class ProjectsController < ApplicationController
auto_actions :all
+ def show
+ @project = find_instance
+ @stories =
+ @project.stories.apply_scopes(:search => [params[:search], :title],
+ :order_by => parse_sort_param(:title, :status))
+ end
+
end
{: .diff .ruby .dryml}
::: ./app/views/projects/show.dryml
new file mode 100644
@@ -0,0 +1,7 @@
+<show-page>
+ <collection: replace>
+ <table-plus with="&@stories" fields="this, tasks.count, status">
+ <empty-message:>No stories match your criteria</empty-message:>
+ </table-plus>
+ </collection:>
+</show-page>
{: .diff .ruby .dryml}
::: ./app/views/taglibs/application.dryml
@@ -8,4 +8,12 @@
<def tag="app-name">Agility</def>
-
+<extend tag="card" for="Task">
+ <old-card merge>
+ <append-body:>
+ <div class="users">
+ Assigned users: <repeat:users join=", "><a/></repeat><else>None</else>
+ </div>
+ </append-body:>
+ </old-card>
+</extend>
{: .diff .ruby .dryml}
::: ./app/views/users/show.dryml
new file mode 100644
@@ -0,0 +1,9 @@
+<show-page>
+ <content-body:>
+ <h3><Your/> Assigned Tasks</h3>
+ <repeat with="&@user.tasks.group_by(&:story)">
+ <h4>Story: <a with="&this_key"/></h4>
+ <collection/>
+ </repeat>
+ </content-body:>
+</show-page>
{: .diff .ruby .dryml}
# Add User Activation
{: .bash}
::: ./app/models/user.rb
@@ -20,12 +20,16 @@ class User < ActiveRecord::Base
# --- Signup lifecycle --- #
lifecycle do
-
- state :active, :default => true
+ state :inactive, :default => true
+ state :active
create :signup, :available_to => "Guest",
:params => [:name, :email_address, :password, :password_confirmation],
- :become => :active
+ :become => :inactive, :new_key => true do
+ UserMailer.deliver_activation(self, lifecycle.key)
+ end
+
+ transition :activate, { :inactive => :active }, :available_to => :key_holder
transition :request_password_reset, { :active => :active }, :new_key => true do
UserMailer.deliver_forgot_password(self, lifecycle.key)
{: .diff .ruby .dryml}
::: ./app/models/user_mailer.rb
@@ -11,4 +11,15 @@ class UserMailer < ActionMailer::Base
@headers = {}
end
+ def activation(user, key)
+ host = Hobo::Controller.request_host
+ app_name = Hobo::Controller.app_name || host
+ @subject = "#{app_name} -- activate"
+ @body = { :user => user, :key => key, :host => host, :app_name => app_name }
+ @recipients = user.email_address
+ @from = "no-reply@#{host}"
+ @sent_on = Time.now
+ @headers = {}
+ end
+
end
{: .diff .ruby .dryml}
::: ./app/views/user_mailer/activation.erb
new file mode 100644
@@ -0,0 +1,9 @@
+<%= @user %>,
+
+To activate your account for <%= @app_name %>, click on this link:
+
+ <%= user_activate_url :host => @host, :id => @user, :key => @key %>
+
+Thank you,
+
+The <%= @app_name %> team.
{: .diff .ruby .dryml}
::: ./config/initializers/mailer.rb
new file mode 100644
@@ -0,0 +1,9 @@
+ActionMailer::Base.delivery_method = :smtp
+ActionMailer::Base.smtp_settings = {
+# :address => "smtp.example.com",
+# :port => 25,
+# :domain => "example.com",
+# :authentication => :login,
+# :user_name => "username",
+# :password => "password",
+}
{: .diff .ruby .dryml}
### Run the Hobo Migrator
$ ./script/generate hobo_migration user_activation --default-name --migrate
{: .bash}
# Odds and ends
## Generate StoryStatus resource
$ ./script/generate hobo_model_resource story_status name:string
{: .bash}
## Story Status association/action/permission
{: .bash}
::: ./app/controllers/story_statuses_controller.rb
@@ -2,6 +2,6 @@ class StoryStatusesController < ApplicationController
hobo_model_controller
- auto_actions :all
+ auto_actions :write_only, :new, :index
end
{: .diff .ruby .dryml}
::: ./app/models/story.rb
@@ -5,11 +5,11 @@ class Story < ActiveRecord::Base
fields do
title :string
body :text
- status :string
timestamps
end
belongs_to :project
+ belongs_to :status, :class_name => "StoryStatus"
has_many :tasks, :dependent => :destroy
@@ -31,4 +31,9 @@ class Story < ActiveRecord::Base
true
end
+ # force 'new' status at create.
+ def before_create
+ self.status = @default_status ||= StoryStatus.find_by_name( 'new' )
+ end
+
end
{: .diff .ruby .dryml}
::: ./app/views/stories/show.dryml
new file mode 100644
@@ -0,0 +1,3 @@
+<show-page>
+ <field-list: tag="editor"/>
+</show-page>
{: .diff .ruby .dryml}
::: ./db/seeds.rb
new file mode 100644
@@ -0,0 +1,3 @@
+# Story Status
+statuses = %w(new accepted discussion implementation user_testing deployed rejected)
+statuses.each { |status| StoryStatus.create :name => status }
{: .diff .ruby .dryml}
### Run the Hobo Migrator
$ ./script/generate hobo_migration story_status_model --default-name --force-drop --migrate
### Use the new (Rails 3.0) rake task to seed the storystatus table
$ rake -s db:seed
{: .bash}
## Filtering stories by status
{: .bash}
::: ./app/controllers/projects_controller.rb
@@ -8,6 +8,7 @@ class ProjectsController < ApplicationController
@project = find_instance
@stories =
@project.stories.apply_scopes(:search => [params[:search], :title],
+ :status_is => params[:status],
:order_by => parse_sort_param(:title, :status))
end
{: .diff .ruby .dryml}
::: ./app/views/projects/show.dryml
@@ -1,6 +1,11 @@
<show-page>
<collection: replace>
<table-plus with="&@stories" fields="this, tasks.count, status">
+ <prepend-header:>
+ <div class="filter">
+ Display by status: <filter-menu param-name="status" options="&StoryStatus.all"/>
+ </div>
+ </prepend-header:>
<empty-message:>No stories match your criteria</empty-message:>
</table-plus>
</collection:>
{: .diff .ruby .dryml}
::: ./public/stylesheets/application.css
@@ -0,0 +1,2 @@
+.show-page.project .filter {float: left;}
+.show-page.project .filter form, .show-page.project .filter form div {display: inline;}
{: .diff .ruby .dryml}
{: .bash}
## Add Acts as List plugin
$ ./script/plugin install acts_as_list
{: .bash}
::: ./app/models/story.rb
@@ -11,7 +11,7 @@ class Story < ActiveRecord::Base
belongs_to :project
belongs_to :status, :class_name => "StoryStatus"
- has_many :tasks, :dependent => :destroy
+ has_many :tasks, :dependent => :destroy, :order => :position
# --- Permissions --- #
{: .diff .ruby .dryml}
::: ./app/models/task.rb
@@ -9,6 +9,9 @@ class Task < ActiveRecord::Base
belongs_to :story
+ acts_as_list :scope => :story
+ attr_protected :position
+
has_many :task_assignments, :dependent => :destroy
has_many :users, :through => :task_assignments, :accessible => true
{: .diff .ruby .dryml}
::: ./app/views/tasks/edit.dryml
new file mode 100644
@@ -0,0 +1,5 @@
+<edit-page>
+ <form:>
+ <cancel: with="&this.story"/>
+ </form:>
+</edit-page>
{: .diff .ruby .dryml}
### Run the Hobo Migrator
$ ./script/generate hobo_migration acts_as_list --default-name --migrate
{: .bash}
## Markdown / Textile formatting of stories
{: .bash}
::: ./app/models/story.rb
@@ -4,7 +4,7 @@ class Story < ActiveRecord::Base
fields do
title :string
- body :text
+ body :markdown
timestamps
end
{: .diff .ruby .dryml}
::: ./config/environment.rb
@@ -9,6 +9,8 @@ require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
config.gem 'hobo'
+ config.gem 'bluecloth'
+
# 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.
{: .diff .ruby .dryml}
$ rake -s gems:install
## Front page touch up.
{: .bash}
::: ./app/views/front/index.dryml
@@ -13,7 +13,9 @@
</section>
</header>
- <section class="content-body">
+ <section class="content-body" if="&logged_in?">
+ <h3>Your Projects</h3>
+ <collection:projects with="&current_user"><card without-creator-link/></collection>
</section>
</content:>
{: .diff .ruby .dryml}
# Project ownership
{: .bash}
::: ./app/models/project.rb
@@ -9,18 +9,20 @@ class Project < ActiveRecord::Base
has_many :stories, :dependent => :destroy
+ belongs_to :owner, :class_name => "User", :creator => true
+
# --- Permissions --- #
def create_permitted?
- acting_user.administrator?
+ owner_is? acting_user
end
def update_permitted?
- acting_user.administrator?
+ acting_user.administrator? || (owner_is?(acting_user) && !owner_changed?)
end
def destroy_permitted?
- acting_user.administrator?
+ acting_user.administrator? || owner_is?(acting_user)
end
def view_permitted?(field)
{: .diff .ruby .dryml}
::: ./app/models/user.rb
@@ -11,6 +11,7 @@ class User < ActiveRecord::Base
has_many :task_assignments, :dependent => :destroy
has_many :tasks, :through => :task_assignments
+ has_many :projects, :class_name => "Project", :foreign_key => "owner_id"
# This gives admin rights to the first sign-up.
# Just remove it if you don't want that
{: .diff .ruby .dryml}
### Project Ownership - migration
$ ./script/generate hobo_migration project_ownership --default-name --migrate
{: .bash}
## Granting read access to others
$ ./script/generate hobo_model_resource project_membership
{: .bash}
## Project Membership - association/action
{: .bash}
::: ./app/controllers/project_memberships_controller.rb
@@ -2,6 +2,6 @@ class ProjectMembershipsController < ApplicationController
hobo_model_controller
- auto_actions :all
+ auto_actions :write_only
end
{: .diff .ruby .dryml}
::: ./app/models/project_membership.rb
@@ -6,6 +6,8 @@ class ProjectMembership < ActiveRecord::Base
timestamps
end
+ belongs_to :project
+ belongs_to :user
# --- Permissions --- #
{: .diff .ruby .dryml}
### Project Membership migration
$ ./script/generate hobo_migration project_memberships --default-name --migrate
{: .bash}
## Project Membership associations/actions/permissions
{: .bash}
::: ./app/controllers/projects_controller.rb
@@ -2,7 +2,9 @@ class ProjectsController < ApplicationController
hobo_model_controller
- auto_actions :all
+ auto_actions :show, :edit, :update, :destroy
+
+ auto_actions_for :owner, [:new, :create]
def show
@project = find_instance
{: .diff .ruby .dryml}
::: ./app/models/project.rb
@@ -8,6 +8,8 @@ class Project < ActiveRecord::Base
end
has_many :stories, :dependent => :destroy
+ has_many :memberships, :class_name => "ProjectMembership", :dependent => :destroy
+ has_many :members, :through => :memberships, :source => :user
belongs_to :owner, :class_name => "User", :creator => true
@@ -26,7 +28,7 @@ class Project < ActiveRecord::Base
end
def view_permitted?(field)
- true
+ acting_user.administrator? || acting_user == owner || acting_user.in?(members)
end
end
{: .diff .ruby .dryml}
::: ./app/models/project_membership.rb
@@ -12,15 +12,15 @@ class ProjectMembership < ActiveRecord::Base
# --- Permissions --- #
def create_permitted?
- acting_user.administrator?
+ acting_user.administrator? || project.owner_is?(acting_user)
end
def update_permitted?
- acting_user.administrator?
+ acting_user.administrator? || project.owner_is?(acting_user)
end
def destroy_permitted?
- acting_user.administrator?
+ acting_user.administrator? || project.owner_is?(acting_user)
end
def view_permitted?(field)
{: .diff .ruby .dryml}
::: ./app/models/story.rb
@@ -28,7 +28,7 @@ class Story < ActiveRecord::Base
end
def view_permitted?(field)
- true
+ project.viewable_by?(acting_user)
end
# force 'new' status at create.
{: .diff .ruby .dryml}
::: ./app/models/task.rb
@@ -30,7 +30,7 @@ class Task < ActiveRecord::Base
end
def view_permitted?(field)
- true
+ story.viewable_by?(acting_user)
end
end
{: .diff .ruby .dryml}
::: ./app/models/user.rb
@@ -12,6 +12,8 @@ class User < ActiveRecord::Base
has_many :task_assignments, :dependent => :destroy
has_many :tasks, :through => :task_assignments
has_many :projects, :class_name => "Project", :foreign_key => "owner_id"
+ has_many :project_memberships, :dependent => :destroy
+ has_many :joined_projects, :through => :project_memberships, :source => :project
# This gives admin rights to the first sign-up.
# Just remove it if you don't want that
{: .diff .ruby .dryml}
## Project Contributor associations/actions/permissions
{: .bash}
::: ./app/controllers/projects_controller.rb
@@ -6,6 +6,11 @@ class ProjectsController < ApplicationController
auto_actions_for :owner, [:new, :create]
+ autocomplete :new_member_name do
+ project = find_instance
+ hobo_completions :name, User.without_project(project).is_not(project.owner)
+ end
+
def show
@project = find_instance
@stories =
{: .diff .ruby .dryml}
::: ./app/models/project.rb
@@ -13,6 +13,14 @@ class Project < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :creator => true
+ has_many :contributor_memberships, :class_name => "ProjectMembership", :scope => :contributor
+ has_many :contributors, :through => :contributor_memberships, :source => :user
+
+ # permission helper
+ def accepts_changes_from?(user)
+ user.administrator? || user == owner || user.in?(contributors)
+ end
+
# --- Permissions --- #
def create_permitted?
@@ -20,7 +28,7 @@ class Project < ActiveRecord::Base
end
def update_permitted?
- acting_user.administrator? || (owner_is?(acting_user) && !owner_changed?)
+ accepts_changes_from?(acting_user) && !owner_changed?
end
def destroy_permitted?
{: .diff .ruby .dryml}
::: ./app/models/project_membership.rb
@@ -3,6 +3,7 @@ class ProjectMembership < ActiveRecord::Base
hobo_model # Don't put anything above this
fields do
+ contributor :boolean, :default => false
timestamps
end
{: .diff .ruby .dryml}
::: ./app/models/story.rb
@@ -16,15 +16,15 @@ class Story < ActiveRecord::Base
# --- Permissions --- #
def create_permitted?
- acting_user.administrator?
+ project.creatable_by?(acting_user)
end
def update_permitted?
- acting_user.signed_up? && !project_changed?
+ project.updatable_by?(acting_user)
end
def destroy_permitted?
- acting_user.administrator?
+ project.destroyable_by?(acting_user)
end
def view_permitted?(field)
{: .diff .ruby .dryml}
::: ./app/models/task.rb
@@ -18,15 +18,15 @@ class Task < ActiveRecord::Base
# --- Permissions --- #
def create_permitted?
- acting_user.administrator?
+ story.creatable_by?(acting_user)
end
def update_permitted?
- acting_user.signed_up? && !story_changed?
+ story.updatable_by?(acting_user)
end
def destroy_permitted?
- acting_user.administrator?
+ story.destroyable_by?(acting_user)
end
def view_permitted?(field)
{: .diff .ruby .dryml}
::: ./app/models/task_assignment.rb
@@ -12,19 +12,19 @@ class TaskAssignment < ActiveRecord::Base
# --- Permissions --- #
def create_permitted?
- acting_user.administrator?
+ task.creatable_by?(acting_user)
end
def update_permitted?
- acting_user.administrator?
+ task.updatable_by?(acting_user)
end
def destroy_permitted?
- acting_user.administrator?
+ task.destroyable_by?(acting_user)
end
def view_permitted?(field)
- true
+ task.viewable_by?(acting_user)
end
end
{: .diff .ruby .dryml}
::: ./app/viewhints/project_hints.rb
@@ -1,4 +1,3 @@
class ProjectHints < Hobo::ViewHints
-
-
+ children :stories, :memberships
end
{: .diff .ruby .dryml}
### Project Contributor migration
$ ./script/generate hobo_migration project_contributorships --default-name --migrate
{: .bash}
## Project Contributor view layer
{: .bash}
::: ./app/views/front/index.dryml
@@ -13,9 +13,14 @@
</section>
</header>
- <section class="content-body" if="&logged_in?">
+ <section with="&current_user" class="content-body" if="&logged_in?">
<h3>Your Projects</h3>
- <collection:projects with="&current_user"><card without-creator-link/></collection>
+ <collection:projects><card without-creator-link/></collection>
+
+ <a:projects action="new">New Project</a>
+
+ <h3>Projects you have joined</h3>
+ <collection:joined-projects><card without-creator-link/></collection>
</section>
</content:>
{: .diff .ruby .dryml}
::: ./app/views/projects/show.dryml
@@ -9,4 +9,19 @@
<empty-message:>No stories match your criteria</empty-message:>
</table-plus>
</collection:>
+
+ <aside:>
+ <h2>Project Members</h2>
+ <collection:memberships part="members">
+ <card><heading:><a:user/></heading:></card>
+ </collection>
+
+ <form:memberships.new update="members" reset-form refocus-form>
+ <div>
+ Add a member:
+ <name-one:user complete-target="&@project" completer="new_member_name"/>
+ </div>
+ </form>
+ </aside:>
+
</show-page>
{: .diff .ruby .dryml}
::: ./app/views/taglibs/application.dryml
@@ -17,3 +17,13 @@
</append-body:>
</old-card>
</extend>
+
+<extend tag="card" for="ProjectMembership">
+ <old-card merge>
+ <body:>
+ <span>Contributor?</span>
+ <editor:contributor/>
+ </body:>
+ </old-card>
+</extend>
+
{: .diff .ruby .dryml}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment