commiting before i fallasleep
This commit is contained in:
parent
6d31b3e4b5
commit
a950d216ec
@ -2,6 +2,8 @@
|
|||||||
# Likewise, all the methods added will be available for all controllers.
|
# Likewise, all the methods added will be available for all controllers.
|
||||||
|
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
|
before_filter :authenticate
|
||||||
|
include Clearance::Authentication
|
||||||
#helper :all # include all helpers, all the time
|
#helper :all # include all helpers, all the time
|
||||||
protect_from_forgery # See ActionController::RequestForgeryProtection for details
|
protect_from_forgery # See ActionController::RequestForgeryProtection for details
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
class ProjectsController < ApplicationController
|
class ProjectsController < ApplicationController
|
||||||
|
before_filter :authenticate
|
||||||
# GET /projects
|
# GET /projects
|
||||||
# GET /projects.xml
|
# GET /projects.xml
|
||||||
def index
|
def index
|
||||||
@projects = Project.all
|
@projects = Project.active.paginate :per_page => 30, :page => params[:page]
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html # index.html.erb
|
format.html # index.html.erb
|
||||||
@ -13,8 +14,8 @@ class ProjectsController < ApplicationController
|
|||||||
# GET /projects/1
|
# GET /projects/1
|
||||||
# GET /projects/1.xml
|
# GET /projects/1.xml
|
||||||
def show
|
def show
|
||||||
@project = Project.find(params[:id])
|
@project = Project.find(params[:id], :include => [:users])
|
||||||
|
@tasks = @project.tasks.parents.paginate(:per_page => 30, :page => params[:page], :include => :tasks)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html # show.html.erb
|
format.html # show.html.erb
|
||||||
format.xml { render :xml => @project }
|
format.xml { render :xml => @project }
|
||||||
|
25
app/controllers/users_controller.rb
Normal file
25
app/controllers/users_controller.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
class UsersController < Clearance::UsersController
|
||||||
|
|
||||||
|
def dashboard
|
||||||
|
@user = current_user
|
||||||
|
@projects = @user.projects.active.paginate(:per_page => 30, :page => params[:page])
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@user = User.find_by_short_name(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def inline
|
||||||
|
|
||||||
|
user = current_user
|
||||||
|
old = user.send(params[:id])
|
||||||
|
user.update_attribute(params[:id], params['value'])
|
||||||
|
if user.save
|
||||||
|
value = user.send(params[:id])
|
||||||
|
else
|
||||||
|
value = old
|
||||||
|
end
|
||||||
|
render :text => value , :layout => false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -7,4 +7,5 @@ module ApplicationHelper
|
|||||||
out << tag(:meta, :name => 'csrf-param', :content => 'authenticity_token')
|
out << tag(:meta, :name => 'csrf-param', :content => 'authenticity_token')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
class Project < ActiveRecord::Base
|
class Project < ActiveRecord::Base
|
||||||
has_many :tasks
|
has_many :tasks
|
||||||
|
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
|
||||||
validates_presence_of :name
|
validates_presence_of :name
|
||||||
|
has_and_belongs_to_many :users
|
||||||
|
validates_uniqueness_of :name
|
||||||
|
|
||||||
|
named_scope :archived, :conditions => {:archived => true}
|
||||||
|
named_scope :active, :conditions => {:archived => false}
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,31 @@
|
|||||||
class Task < ActiveRecord::Base
|
class Task < ActiveRecord::Base
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
has_many :tasks, :foreign_key => :parent
|
has_many :tasks, :foreign_key => :parent
|
||||||
|
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
|
||||||
|
belongs_to :assignee, :class_name => "User", :foreign_key => 'assigned_id'
|
||||||
|
#named scopes
|
||||||
named_scope :parents, :conditions => {:parent => nil}
|
named_scope :parents, :conditions => {:parent => nil}
|
||||||
|
#validations
|
||||||
validates_presence_of :name, :description, :project_id
|
validates_presence_of :name, :description, :project_id
|
||||||
validates_associated :project
|
validates_associated :project
|
||||||
|
validate :assignee_validation, :owner_validation
|
||||||
|
|
||||||
|
def assignee_validation
|
||||||
|
unless assigned_id.blank?
|
||||||
|
unless User.exists?(assigned_id)
|
||||||
|
errors.add :assignee, "Assignee doesn't exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def owner_validation
|
||||||
|
unless owner_id.blank?
|
||||||
|
unless User.exists?(owner_id)
|
||||||
|
errors.add :owner, "Owner doesn't exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
17
app/models/user.rb
Normal file
17
app/models/user.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class User < ActiveRecord::Base
|
||||||
|
include Clearance::User
|
||||||
|
has_and_belongs_to_many :projects
|
||||||
|
has_many :owned_projects, :class_name => "Project", :foreign_key => "owner_id"
|
||||||
|
has_many :owned_tasks, :class_name => "Task", :foreign_key => "owner_id"
|
||||||
|
has_many :assigned_tasks, :class_name => "Task", :foreign_key => "assigned_id"
|
||||||
|
validates_uniqueness_of :short_name
|
||||||
|
validates_presence_of :short_name
|
||||||
|
validates_format_of :phone, :with => /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/, :if => Proc.new {|user| !user.phone.blank?}
|
||||||
|
validates_format_of :mobile, :with => /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/, :if => Proc.new {|user| !user.mobile.blank?}
|
||||||
|
|
||||||
|
def gravatar(size = 48)
|
||||||
|
hash = Digest::MD5.hexdigest email
|
||||||
|
"http://www.gravatar.com/avatar/#{hash}.jpg?s=#{size}"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -7,13 +7,18 @@
|
|||||||
<%= stylesheet_link_tag 'scaffold', 'style' %>
|
<%= stylesheet_link_tag 'scaffold', 'style' %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id='header'>
|
||||||
|
Collab
|
||||||
|
</div>
|
||||||
<% flash.each_key do |flash_key| %>
|
<% flash.each_key do |flash_key| %>
|
||||||
<div id='flash_<%= flash_key.to_s %>'>
|
<div id='flash_<%=h flash_key.to_s %>' class='flash'>
|
||||||
<%= flash[flash_key] %>
|
<%=h flash[flash_key] %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<div id='wrapper'>
|
||||||
<%= yield %>
|
<div id='content'>
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
21
app/views/passwords/edit.html.erb
Normal file
21
app/views/passwords/edit.html.erb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<h2>Change your password</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Your password has been reset. Choose a new password below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<% semantic_form_for(:user,
|
||||||
|
:url => user_password_path(@user, :token => @user.confirmation_token),
|
||||||
|
:html => { :method => :put }) do |form| %>
|
||||||
|
<%= form.error_messages %>
|
||||||
|
<% form.inputs do -%>
|
||||||
|
<%= form.input :password, :as => :password,
|
||||||
|
:label => "Choose password" %>
|
||||||
|
<%= form.input :password_confirmation, :as => :password,
|
||||||
|
:label => "Confirm password" %>
|
||||||
|
<% end -%>
|
||||||
|
<% form.buttons do -%>
|
||||||
|
<%= form.commit_button "Save this password" %>
|
||||||
|
<% end -%>
|
||||||
|
<% end %>
|
||||||
|
|
15
app/views/passwords/new.html.erb
Normal file
15
app/views/passwords/new.html.erb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<h2>Reset your password</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We will email you a link to reset your password.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<% semantic_form_for :password, :url => passwords_path do |form| -%>
|
||||||
|
<% form.inputs do -%>
|
||||||
|
<%= form.input :email, :label => "Email address" %>
|
||||||
|
<% end -%>
|
||||||
|
<% form.buttons do -%>
|
||||||
|
<%= form.commit_button "Reset password" %>
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
|
@ -8,7 +8,7 @@
|
|||||||
<% @projects.each do |project| %>
|
<% @projects.each do |project| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=h project.name %></td>
|
<td><%=h project.name %></td>
|
||||||
<td><%= link_to 'Show', project %></td>
|
<td><%= link_to 'Show', project_path(project) %></td>
|
||||||
<td><%= link_to 'Edit', edit_project_path(project) %></td>
|
<td><%= link_to 'Edit', edit_project_path(project) %></td>
|
||||||
<td><%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %></td>
|
<td><%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %></td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -17,4 +17,6 @@
|
|||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<%= will_paginate @projects %>
|
||||||
|
|
||||||
<%= link_to 'New project', new_project_path %>
|
<%= link_to 'New project', new_project_path %>
|
@ -2,7 +2,14 @@
|
|||||||
<b>Name:</b>
|
<b>Name:</b>
|
||||||
<%=h @project.name %>
|
<%=h @project.name %>
|
||||||
</p>
|
</p>
|
||||||
|
<div id='project_users'>
|
||||||
|
<% @project.users.each do |user| %>
|
||||||
|
<%= image_tag user.gravatar %> <%= link_to "@#{user.short_name}", user_path(user.short_name) %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div id='project_tasks'>
|
||||||
|
<%= render :partial => 'tasks/task_list' %>
|
||||||
|
</div>
|
||||||
|
<%= will_paginate @tasks %>
|
||||||
<%= link_to 'Edit', edit_project_path(@project) %> |
|
<%= link_to 'Edit', edit_project_path(@project) %> |
|
||||||
<%= link_to 'Back', projects_path %>
|
<%= link_to 'Back', projects_path %>
|
21
app/views/sessions/new.html.erb
Normal file
21
app/views/sessions/new.html.erb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<h2>Sign in</h2>
|
||||||
|
|
||||||
|
<% semantic_form_for :session, :url => session_path do |form| %>
|
||||||
|
<% form.inputs do %>
|
||||||
|
<%= form.input :email %>
|
||||||
|
<%= form.input :password, :as => :password %>
|
||||||
|
<% end %>
|
||||||
|
<% form.buttons do %>
|
||||||
|
<%= form.commit_button "Sign in" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<%= link_to "Sign up", sign_up_path %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link_to "Forgot password?", new_password_path %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
@ -21,5 +21,4 @@
|
|||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p> No Dependet Tasks </p>
|
<p> No Dependet Tasks </p>
|
||||||
<p><%= link_to 'Create One', new_project_task_path(@project, :parent => @task.id) %>
|
|
||||||
<% end %>
|
<% end %>
|
7
app/views/users/_inputs.html.erb
Normal file
7
app/views/users/_inputs.html.erb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<% form.inputs do %>
|
||||||
|
<%= form.input :email %>
|
||||||
|
<%= form.input :password %>
|
||||||
|
<%= form.input :password_confirmation, :label => "Confirm password" %>
|
||||||
|
<%= form.input :short_name %>
|
||||||
|
<% end %>
|
||||||
|
|
18
app/views/users/dashboard.html.erb
Normal file
18
app/views/users/dashboard.html.erb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<%= javascript_include_tag 'jeditable.min'%>
|
||||||
|
<script type='text/javascript'>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#phone').editable('<%= user_inline_edit_path('phone') %>', {col:'phone'});
|
||||||
|
$('#mobile').editable('<%= user_inline_edit_path('mobile') %>', {col:'phone'});
|
||||||
|
$('#im').editable('<%= user_inline_edit_path('im') %>', {col:'phone'});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<%= image_tag @user.gravatar %>
|
||||||
|
<div id='dashboard_user_info'>
|
||||||
|
<% [:phone, :mobile, :im].each do |col| %>
|
||||||
|
<p><%= col.to_s %>: <span id='<%= col.to_s %>'><%=h @user.send(col) %></span></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% @projects.each do |project| %>
|
||||||
|
<p><%= link_to project.name, project %></p>
|
||||||
|
<% end %>
|
10
app/views/users/new.html.erb
Normal file
10
app/views/users/new.html.erb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<h2>Sign up</h2>
|
||||||
|
|
||||||
|
<% semantic_form_for @user do |form| %>
|
||||||
|
<%= form.error_messages %>
|
||||||
|
<%= render :partial => "/users/inputs", :locals => { :form => form } %>
|
||||||
|
<% form.buttons do %>
|
||||||
|
<%= form.commit_button "Sign up" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
1
app/views/users/show.html.erb
Normal file
1
app/views/users/show.html.erb
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1>User Activity</h1>
|
0
assigned_id
Normal file
0
assigned_id
Normal file
@ -25,8 +25,8 @@ Rails::Initializer.run do |config|
|
|||||||
config.gem 'paperclip'
|
config.gem 'paperclip'
|
||||||
config.gem 'formtastic'
|
config.gem 'formtastic'
|
||||||
config.gem 'faker'
|
config.gem 'faker'
|
||||||
config.gem 'postgres'
|
|
||||||
config.gem 'mysql'
|
config.gem 'mysql'
|
||||||
|
config.gem "matthuhiggins-foreigner", :lib => "foreigner", :source => "http://gemcutter.org"
|
||||||
|
|
||||||
# Only load the plugins named here, in the order given (default is alphabetical).
|
# Only load the plugins named here, in the order given (default is alphabetical).
|
||||||
# :all can be used as a placeholder for all plugins not explicitly named
|
# :all can be used as a placeholder for all plugins not explicitly named
|
||||||
|
@ -14,4 +14,6 @@ config.action_view.debug_rjs = true
|
|||||||
config.action_controller.perform_caching = false
|
config.action_controller.perform_caching = false
|
||||||
|
|
||||||
# Don't care if the mailer can't send
|
# Don't care if the mailer can't send
|
||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
|
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
|
@ -25,4 +25,6 @@ config.action_mailer.delivery_method = :test
|
|||||||
# Use SQL instead of Active Record's schema dumper when creating the test database.
|
# Use SQL instead of Active Record's schema dumper when creating the test database.
|
||||||
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
||||||
# like if you have constraints or database-specific column types
|
# like if you have constraints or database-specific column types
|
||||||
# config.active_record.schema_format = :sql
|
# config.active_record.schema_format = :sql
|
||||||
|
|
||||||
|
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
|
3
config/initializers/clearance.rb
Normal file
3
config/initializers/clearance.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Clearance.configure do |config|
|
||||||
|
config.mailer_sender = 'donotreply@example.com'
|
||||||
|
end
|
@ -1,40 +1,10 @@
|
|||||||
ActionController::Routing::Routes.draw do |map|
|
ActionController::Routing::Routes.draw do |map|
|
||||||
# The priority is based upon order of creation: first created -> highest priority.
|
Clearance::Routes.draw(map)
|
||||||
|
map.user '/users/:id', :controller => :users, :action => :show
|
||||||
|
map.user_inline_edit '/users/:id/inline', :controller => :users, :action => :inline, :conditions => { :method => :post }
|
||||||
|
#map.dashboard '/dashboard', :controller => :users, :action => :dashboard
|
||||||
|
|
||||||
# Sample of regular route:
|
|
||||||
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
|
|
||||||
# Keep in mind you can assign values other than :controller and :action
|
|
||||||
|
|
||||||
# Sample of named route:
|
|
||||||
# map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
|
|
||||||
# This route can be invoked with purchase_url(:id => product.id)
|
|
||||||
|
|
||||||
# Sample resource route (maps HTTP verbs to controller actions automatically):
|
|
||||||
# map.resources :products
|
|
||||||
|
|
||||||
# Sample resource route with options:
|
|
||||||
# map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
|
|
||||||
|
|
||||||
# Sample resource route with sub-resources:
|
|
||||||
# map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
|
|
||||||
|
|
||||||
# Sample resource route with more complex sub-resources
|
|
||||||
# map.resources :products do |products|
|
|
||||||
# products.resources :comments
|
|
||||||
# products.resources :sales, :collection => { :recent => :get }
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Sample resource route within a namespace:
|
|
||||||
# map.namespace :admin do |admin|
|
|
||||||
# # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
|
|
||||||
# admin.resources :products
|
|
||||||
# end
|
|
||||||
|
|
||||||
# You can have the root of your site routed with map.root -- just remember to delete public/index.html.
|
|
||||||
# map.root :controller => "welcome"
|
|
||||||
|
|
||||||
# See how all your routes lay out with "rake routes"
|
|
||||||
|
|
||||||
map.resources :projects do |projects|
|
map.resources :projects do |projects|
|
||||||
projects.resources :tasks
|
projects.resources :tasks
|
||||||
end
|
end
|
||||||
@ -45,5 +15,5 @@ ActionController::Routing::Routes.draw do |map|
|
|||||||
# consider removing or commenting them out if you're using named routes and resources.
|
# consider removing or commenting them out if you're using named routes and resources.
|
||||||
#map.connect ':controller/:action/:id'
|
#map.connect ':controller/:action/:id'
|
||||||
#map.connect ':controller/:action/:id.:format'
|
#map.connect ':controller/:action/:id.:format'
|
||||||
map.root :controller => :projects, :action =>:index
|
map.root :controller => :users, :action => :dashboard
|
||||||
end
|
end
|
||||||
|
21
db/migrate/20100305002309_clearance_create_users.rb
Normal file
21
db/migrate/20100305002309_clearance_create_users.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class ClearanceCreateUsers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table(:users) do |t|
|
||||||
|
t.string :email
|
||||||
|
t.string :encrypted_password, :limit => 128
|
||||||
|
t.string :salt, :limit => 128
|
||||||
|
t.string :confirmation_token, :limit => 128
|
||||||
|
t.string :remember_token, :limit => 128
|
||||||
|
t.boolean :email_confirmed, :default => false, :null => false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :users, [:id, :confirmation_token]
|
||||||
|
add_index :users, :email
|
||||||
|
add_index :users, :remember_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :users
|
||||||
|
end
|
||||||
|
end
|
15
db/migrate/20100305010025_projects_users.rb
Normal file
15
db/migrate/20100305010025_projects_users.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
class ProjectsUsers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :projects_users, :id => false do |t|
|
||||||
|
t.integer :project_id
|
||||||
|
t.integer :user_id
|
||||||
|
t.foreign_key :projects
|
||||||
|
t.foreign_key :users
|
||||||
|
t.index :project_id, :user_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :projects_users
|
||||||
|
end
|
||||||
|
end
|
9
db/migrate/20100305013414_add_shorname_to_users.rb
Normal file
9
db/migrate/20100305013414_add_shorname_to_users.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class AddShornameToUsers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
add_column :users, :short_name, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_column :users, :short_name
|
||||||
|
end
|
||||||
|
end
|
21
db/migrate/20100305020706_add_owner_to_tasks_and_projects.rb
Normal file
21
db/migrate/20100305020706_add_owner_to_tasks_and_projects.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class AddOwnerToTasksAndProjects < ActiveRecord::Migration
|
||||||
|
@tables = [:tasks, :projects]
|
||||||
|
|
||||||
|
def self.up
|
||||||
|
@tables.each do |table|
|
||||||
|
add_column table, :owner_id, :integer
|
||||||
|
add_index table, :owner_id
|
||||||
|
add_foreign_key table, :users, :column => :owner_id
|
||||||
|
end
|
||||||
|
add_column :tasks, :assigned_id, :integer
|
||||||
|
add_index :tasks, :assigned_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
@tables.each do |t|
|
||||||
|
remove_column t, :owner_id
|
||||||
|
remove_foreign_key t, :column => :owner_id
|
||||||
|
end
|
||||||
|
remove_column :tasks, :assigned_id
|
||||||
|
end
|
||||||
|
end
|
9
db/migrate/20100305035342_add_archived_to_projects.rb
Normal file
9
db/migrate/20100305035342_add_archived_to_projects.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class AddArchivedToProjects < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
add_column :projects, :archived, :boolean, :default => false
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_column :projects, :archived
|
||||||
|
end
|
||||||
|
end
|
13
db/migrate/20100305060621_add_contact_info_to_users.rb
Normal file
13
db/migrate/20100305060621_add_contact_info_to_users.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class AddContactInfoToUsers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
add_column :users, :phone, :string
|
||||||
|
add_column :users, :mobile, :string
|
||||||
|
add_column :users, :im, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_column :users, :im
|
||||||
|
remove_column :users, :mobile
|
||||||
|
remove_column :users, :phone
|
||||||
|
end
|
||||||
|
end
|
39
db/schema.rb
39
db/schema.rb
@ -9,12 +9,21 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20100304040201) do
|
ActiveRecord::Schema.define(:version => 20100305060621) do
|
||||||
|
|
||||||
create_table "projects", :force => true do |t|
|
create_table "projects", :force => true do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
|
t.integer "owner_id"
|
||||||
|
t.boolean "archived", :default => false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "projects", ["owner_id"], :name => "index_projects_on_owner_id"
|
||||||
|
|
||||||
|
create_table "projects_users", :id => false, :force => true do |t|
|
||||||
|
t.integer "project_id"
|
||||||
|
t.integer "user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "tasks", :force => true do |t|
|
create_table "tasks", :force => true do |t|
|
||||||
@ -24,6 +33,34 @@ ActiveRecord::Schema.define(:version => 20100304040201) do
|
|||||||
t.integer "parent"
|
t.integer "parent"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
|
t.integer "owner_id"
|
||||||
|
t.integer "assigned_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_index "tasks", ["assigned_id"], :name => "index_tasks_on_assigned_id"
|
||||||
|
add_index "tasks", ["owner_id"], :name => "index_tasks_on_owner_id"
|
||||||
|
|
||||||
|
create_table "users", :force => true do |t|
|
||||||
|
t.string "email"
|
||||||
|
t.string "encrypted_password", :limit => 128
|
||||||
|
t.string "salt", :limit => 128
|
||||||
|
t.string "confirmation_token", :limit => 128
|
||||||
|
t.string "remember_token", :limit => 128
|
||||||
|
t.boolean "email_confirmed", :default => false, :null => false
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
t.string "short_name"
|
||||||
|
t.string "phone"
|
||||||
|
t.string "mobile"
|
||||||
|
t.string "im"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "users", ["email"], :name => "index_users_on_email"
|
||||||
|
add_index "users", ["id", "confirmation_token"], :name => "index_users_on_id_and_confirmation_token"
|
||||||
|
add_index "users", ["remember_token"], :name => "index_users_on_remember_token"
|
||||||
|
|
||||||
|
add_foreign_key "projects", "users", :name => "projects_owner_id_fk", :column => "owner_id"
|
||||||
|
|
||||||
|
add_foreign_key "tasks", "users", :name => "tasks_owner_id_fk", :column => "owner_id"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,32 +1,46 @@
|
|||||||
path = File.join(RAILS_ROOT, 'factories')
|
path = File.join(RAILS_ROOT, 'factories')
|
||||||
require 'factory_girl'
|
require 'factory_girl'
|
||||||
require File.join(path, 'task_factory')
|
["user", "task", "project"].each {|factory| require File.join(path, "#{factory}_factory")}
|
||||||
require File.join(path, 'project_factory')
|
|
||||||
class FactoryLoader
|
class FactoryLoader
|
||||||
|
|
||||||
def self.up
|
def self.up
|
||||||
|
self.create_users
|
||||||
self.create_projects
|
self.create_projects
|
||||||
|
puts "Factories Loaded!"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.down
|
def self.down
|
||||||
[Task, Project].each {|klass| klass.delete_all}
|
[Task, Project, User].each(&:delete_all)
|
||||||
|
puts "Factories Unloaded!"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def self.create_users
|
||||||
|
5.times do |n|
|
||||||
|
u = Factory.create(:user)
|
||||||
|
u.confirm_email!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.create_projects
|
def self.create_projects
|
||||||
101.times do |i|
|
users = User.all
|
||||||
p = Factory.create(:project)
|
users.each do |user|
|
||||||
self.create_tasks(p)
|
10.times do |i|
|
||||||
|
p = Factory.create(:project, :owner => user, :users => [user])
|
||||||
|
self.create_tasks(p, user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_tasks(project)
|
def self.create_tasks(project, user)
|
||||||
5.times do |i|
|
5.times do |i|
|
||||||
task = Factory.create(:task, :project => project)
|
task = Factory.create(:task, :project => project, :owner => user, :assignee => user)
|
||||||
5.times do |n|
|
5.times do |n|
|
||||||
t = Factory.create(:task, :project => project, :parent => task.id)
|
t = Factory.create(:task, :project => project, :parent => task.id, :owner => user, :assignee => user)
|
||||||
2.times do |nn|
|
2.times do |nn|
|
||||||
Factory.create(:task, :project => project, :parent => t.id)
|
Factory.create(:task, :project => project, :parent => t.id, :owner => user, :assignee => user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
8
factories/user_factory.rb
Normal file
8
factories/user_factory.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
require 'factory_girl'
|
||||||
|
|
||||||
|
|
||||||
|
Factory.define :user do |f|
|
||||||
|
f.sequence(:email) { |n| "email_#{n}@localhost.com" }
|
||||||
|
f.sequence(:password) { |n| "password#{n}" }
|
||||||
|
f.sequence(:short_name) { |n| "short_#{n}" }
|
||||||
|
end
|
18
lib/tasks/factory.rake
Normal file
18
lib/tasks/factory.rake
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace :db do
|
||||||
|
namespace :factory do
|
||||||
|
desc "Load the factory data"
|
||||||
|
task :load => [:environment, :load_class] do
|
||||||
|
FactoryLoader.up
|
||||||
|
end
|
||||||
|
desc "Unload all the stories"
|
||||||
|
task :unload => [:environment, :load_class] do
|
||||||
|
FactoryLoader.down
|
||||||
|
end
|
||||||
|
desc "Reload Factories"
|
||||||
|
task :reload => [:environment, :load_class, :unload, :load]
|
||||||
|
|
||||||
|
task :load_class do
|
||||||
|
require File.join(RAILS_ROOT, 'factories', 'factory_loader')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
37
public/javascripts/jeditable.min.js
vendored
Normal file
37
public/javascripts/jeditable.min.js
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
(function($){$.fn.editable=function(target,options){if('disable'==target){$(this).data('disabled.editable',true);return;}
|
||||||
|
if('enable'==target){$(this).data('disabled.editable',false);return;}
|
||||||
|
if('destroy'==target){$(this).unbind($(this).data('event.editable')).removeData('disabled.editable').removeData('event.editable');return;}
|
||||||
|
var settings=$.extend({},$.fn.editable.defaults,{target:target},options);var plugin=$.editable.types[settings.type].plugin||function(){};var submit=$.editable.types[settings.type].submit||function(){};var buttons=$.editable.types[settings.type].buttons||$.editable.types['defaults'].buttons;var content=$.editable.types[settings.type].content||$.editable.types['defaults'].content;var element=$.editable.types[settings.type].element||$.editable.types['defaults'].element;var reset=$.editable.types[settings.type].reset||$.editable.types['defaults'].reset;var callback=settings.callback||function(){};var onedit=settings.onedit||function(){};var onsubmit=settings.onsubmit||function(){};var onreset=settings.onreset||function(){};var onerror=settings.onerror||reset;if(settings.tooltip){$(this).attr('title',settings.tooltip);}
|
||||||
|
settings.autowidth='auto'==settings.width;settings.autoheight='auto'==settings.height;return this.each(function(){var self=this;var savedwidth=$(self).width();var savedheight=$(self).height();$(this).data('event.editable',settings.event);if(!$.trim($(this).html())){$(this).html(settings.placeholder);}
|
||||||
|
$(this).bind(settings.event,function(e){if(true===$(this).data('disabled.editable')){return;}
|
||||||
|
if(self.editing){return;}
|
||||||
|
if(false===onedit.apply(this,[settings,self])){return;}
|
||||||
|
e.preventDefault();e.stopPropagation();if(settings.tooltip){$(self).removeAttr('title');}
|
||||||
|
if(0==$(self).width()){settings.width=savedwidth;settings.height=savedheight;}else{if(settings.width!='none'){settings.width=settings.autowidth?$(self).width():settings.width;}
|
||||||
|
if(settings.height!='none'){settings.height=settings.autoheight?$(self).height():settings.height;}}
|
||||||
|
if($(this).html().toLowerCase().replace(/(;|")/g,'')==settings.placeholder.toLowerCase().replace(/(;|")/g,'')){$(this).html('');}
|
||||||
|
self.editing=true;self.revert=$(self).html();$(self).html('');var form=$('<form />');if(settings.cssclass){if('inherit'==settings.cssclass){form.attr('class',$(self).attr('class'));}else{form.attr('class',settings.cssclass);}}
|
||||||
|
if(settings.style){if('inherit'==settings.style){form.attr('style',$(self).attr('style'));form.css('display',$(self).css('display'));}else{form.attr('style',settings.style);}}
|
||||||
|
var input=element.apply(form,[settings,self]);var input_content;if(settings.loadurl){var t=setTimeout(function(){input.disabled=true;content.apply(form,[settings.loadtext,settings,self]);},100);var loaddata={};loaddata[settings.id]=self.id;if($.isFunction(settings.loaddata)){$.extend(loaddata,settings.loaddata.apply(self,[self.revert,settings]));}else{$.extend(loaddata,settings.loaddata);}
|
||||||
|
$.ajax({type:settings.loadtype,url:settings.loadurl,data:loaddata,async:false,success:function(result){window.clearTimeout(t);input_content=result;input.disabled=false;}});}else if(settings.data){input_content=settings.data;if($.isFunction(settings.data)){input_content=settings.data.apply(self,[self.revert,settings]);}}else{input_content=self.revert;}
|
||||||
|
content.apply(form,[input_content,settings,self]);input.attr('name',settings.name);buttons.apply(form,[settings,self]);$(self).append(form);plugin.apply(form,[settings,self]);$(':input:visible:enabled:first',form).focus();if(settings.select){input.select();}
|
||||||
|
input.keydown(function(e){if(e.keyCode==27){e.preventDefault();reset.apply(form,[settings,self]);}});var t;if('cancel'==settings.onblur){input.blur(function(e){t=setTimeout(function(){reset.apply(form,[settings,self]);},500);});}else if('submit'==settings.onblur){input.blur(function(e){t=setTimeout(function(){form.submit();},200);});}else if($.isFunction(settings.onblur)){input.blur(function(e){settings.onblur.apply(self,[input.val(),settings]);});}else{input.blur(function(e){});}
|
||||||
|
form.submit(function(e){if(t){clearTimeout(t);}
|
||||||
|
e.preventDefault();if(false!==onsubmit.apply(form,[settings,self])){if(false!==submit.apply(form,[settings,self])){if($.isFunction(settings.target)){var str=settings.target.apply(self,[input.val(),settings]);$(self).html(str);self.editing=false;callback.apply(self,[self.innerHTML,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}}else{var submitdata={};submitdata[settings.name]=input.val();submitdata[settings.id]=self.id;if($.isFunction(settings.submitdata)){$.extend(submitdata,settings.submitdata.apply(self,[self.revert,settings]));}else{$.extend(submitdata,settings.submitdata);}
|
||||||
|
if('PUT'==settings.method){submitdata['_method']='put';}
|
||||||
|
$(self).html(settings.indicator);var ajaxoptions={type:'POST',data:submitdata,dataType:'html',url:settings.target,success:function(result,status){if(ajaxoptions.dataType=='html'){$(self).html(result);}
|
||||||
|
self.editing=false;callback.apply(self,[result,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}},error:function(xhr,status,error){onerror.apply(form,[settings,self,xhr]);}};$.extend(ajaxoptions,settings.ajaxoptions);$.ajax(ajaxoptions);}}}
|
||||||
|
$(self).attr('title',settings.tooltip);return false;});});this.reset=function(form){if(this.editing){if(false!==onreset.apply(form,[settings,self])){$(self).html(self.revert);self.editing=false;if(!$.trim($(self).html())){$(self).html(settings.placeholder);}
|
||||||
|
if(settings.tooltip){$(self).attr('title',settings.tooltip);}}}};});};$.editable={types:{defaults:{element:function(settings,original){var input=$('<input type="hidden"></input>');$(this).append(input);return(input);},content:function(string,settings,original){$(':input:first',this).val(string);},reset:function(settings,original){original.reset(this);},buttons:function(settings,original){var form=this;if(settings.submit){if(settings.submit.match(/>$/)){var submit=$(settings.submit).click(function(){if(submit.attr("type")!="submit"){form.submit();}});}else{var submit=$('<button type="submit" />');submit.html(settings.submit);}
|
||||||
|
$(this).append(submit);}
|
||||||
|
if(settings.cancel){if(settings.cancel.match(/>$/)){var cancel=$(settings.cancel);}else{var cancel=$('<button type="cancel" />');cancel.html(settings.cancel);}
|
||||||
|
$(this).append(cancel);$(cancel).click(function(event){if($.isFunction($.editable.types[settings.type].reset)){var reset=$.editable.types[settings.type].reset;}else{var reset=$.editable.types['defaults'].reset;}
|
||||||
|
reset.apply(form,[settings,original]);return false;});}}},text:{element:function(settings,original){var input=$('<input />');if(settings.width!='none'){input.width(settings.width);}
|
||||||
|
if(settings.height!='none'){input.height(settings.height);}
|
||||||
|
input.attr('autocomplete','off');$(this).append(input);return(input);}},textarea:{element:function(settings,original){var textarea=$('<textarea />');if(settings.rows){textarea.attr('rows',settings.rows);}else if(settings.height!="none"){textarea.height(settings.height);}
|
||||||
|
if(settings.cols){textarea.attr('cols',settings.cols);}else if(settings.width!="none"){textarea.width(settings.width);}
|
||||||
|
$(this).append(textarea);return(textarea);}},select:{element:function(settings,original){var select=$('<select />');$(this).append(select);return(select);},content:function(data,settings,original){if(String==data.constructor){eval('var json = '+data);}else{var json=data;}
|
||||||
|
for(var key in json){if(!json.hasOwnProperty(key)){continue;}
|
||||||
|
if('selected'==key){continue;}
|
||||||
|
var option=$('<option />').val(key).append(json[key]);$('select',this).append(option);}
|
||||||
|
$('select',this).children().each(function(){if($(this).val()==json['selected']||$(this).text()==$.trim(original.revert)){$(this).attr('selected','selected');}});}}},addInputType:function(name,input){$.editable.types[name]=input;}};$.fn.editable.defaults={name:'value',id:'id',type:'text',width:'auto',height:'auto',event:'click.editable',onblur:'cancel',loadtype:'GET',loadtext:'Loading...',placeholder:'Click to edit',loaddata:{},submitdata:{},ajaxoptions:{}};})(jQuery);
|
@ -2,16 +2,29 @@
|
|||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
}
|
}
|
||||||
#flash_error {
|
|
||||||
|
div.flash {
|
||||||
|
width:1000px;
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
padding:5px;
|
||||||
|
background-color:white;
|
||||||
|
margin-top:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#flash_error {
|
||||||
color:red;
|
color:red;
|
||||||
|
border:red 5px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
#flash_notice {
|
div#flash_notice {
|
||||||
color:blue;
|
color:blue;
|
||||||
|
border:blue 5px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
#flash_success {
|
div#flash_success {
|
||||||
color:green;
|
color:green;
|
||||||
|
border:green 5px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -21,4 +34,24 @@ table.task_list tr.odd{
|
|||||||
|
|
||||||
table.task_list tr.even{
|
table.task_list tr.even{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color:gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#header {
|
||||||
|
width:1000px;
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
font-size:24;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#wrapper {
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
width:1000px;
|
||||||
|
margin-top:30px;
|
||||||
|
background-color:white;
|
||||||
}
|
}
|
13
test/factories/clearance.rb
Normal file
13
test/factories/clearance.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Factory.sequence :email do |n|
|
||||||
|
"user#{n}@example.com"
|
||||||
|
end
|
||||||
|
|
||||||
|
Factory.define :user do |user|
|
||||||
|
user.email { Factory.next :email }
|
||||||
|
user.password { "password" }
|
||||||
|
user.password_confirmation { "password" }
|
||||||
|
end
|
||||||
|
|
||||||
|
Factory.define :email_confirmed_user, :parent => :user do |user|
|
||||||
|
user.email_confirmed { true }
|
||||||
|
end
|
@ -1,6 +1,8 @@
|
|||||||
ENV["RAILS_ENV"] = "test"
|
ENV["RAILS_ENV"] = "test"
|
||||||
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
||||||
require 'test_help'
|
require 'test_help'
|
||||||
|
require 'factory_girl'
|
||||||
|
require 'factories/factory_loader'
|
||||||
|
|
||||||
class ActiveSupport::TestCase
|
class ActiveSupport::TestCase
|
||||||
# Transactional fixtures accelerate your tests by wrapping each test method
|
# Transactional fixtures accelerate your tests by wrapping each test method
|
||||||
@ -32,7 +34,7 @@ class ActiveSupport::TestCase
|
|||||||
#
|
#
|
||||||
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
||||||
# -- they do not yet inherit this setting
|
# -- they do not yet inherit this setting
|
||||||
fixtures :all
|
#fixtures :all
|
||||||
|
|
||||||
# Add more helper methods to be used by all tests here...
|
# Add more helper methods to be used by all tests here...
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,28 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class TaskTest < ActiveSupport::TestCase
|
class TaskTest < ActiveSupport::TestCase
|
||||||
# Replace this with your real tests.
|
|
||||||
test "the truth" do
|
test "Assignee Fails User Doesn't exist" do
|
||||||
assert true
|
project = Factory.create(:project)
|
||||||
|
task = Factory.create(:task, :project => project)
|
||||||
|
|
||||||
|
task.assigned_id = 12000
|
||||||
|
task.save
|
||||||
|
|
||||||
|
assert !task.valid?
|
||||||
|
assert_equal "Assignee doesn't exist", task.errors.first.last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Owner Fails User Doesn't exist" do
|
||||||
|
project = Factory.create(:project)
|
||||||
|
task = Factory.create(:task, :project => project)
|
||||||
|
|
||||||
|
task.owner_id = 12000
|
||||||
|
task.save
|
||||||
|
|
||||||
|
assert !task.valid?
|
||||||
|
assert_equal "Owner doesn't exist", task.errors.first.last
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user