Merge pull request #381 from warpc/263-refactoring

[Refs #263] Fix tests. Fix comments. Global refactoring. 
* Improve docs, design source and config examples;
* Fix and refactor build lists, comments and other specs;
* Refactor comments controller, models and views;
* Code cleanup.
This commit is contained in:
Vladimir Sharshov 2012-04-05 10:23:43 -07:00
commit b72736b2b8
93 changed files with 767 additions and 639 deletions

View File

@ -45,7 +45,7 @@ class BuildListsController < ApplicationController
@build_list = @project.build_lists.build(params[:build_list])
@build_list.commit_hash = @project.git_repository.commits(@build_list.project_version.match(/^latest_(.+)/).to_a.last || @build_list.project_version).first.id if @build_list.project_version
@build_list.bpl = bpl; @build_list.arch = arch; @build_list.user = current_user
@build_list.include_repos = @build_list.include_repos.select { |ir| @build_list.bpl.repository_ids.include? ir.to_i }
@build_list.include_repos = @build_list.include_repos.select {|ir| @build_list.bpl.repository_ids.include? ir.to_i}
@build_list.priority = 100 # User builds more priority than mass rebuild with zero priority
flash_options = {:project_version => @build_list.project_version, :arch => arch.name, :bpl => bpl.name, :pl => @build_list.pl}
if @build_list.save

View File

@ -1,26 +1,17 @@
# -*- encoding : utf-8 -*-
class CommentsController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource :project
before_filter :find_commentable
before_filter :find_or_build_comment
load_and_authorize_resource
load_resource :project
before_filter :set_commentable
before_filter :find_comment, :only => [:edit, :update, :destroy]
authorize_resource
def index
@comments = @commentable.comments
end
include CommentsHelper
def create
@comment = @commentable.comments.build(params[:comment]) if @commentable.class == Issue
if @commentable.class == Grit::Commit
@comment = Comment.new(params[:comment].merge(:commentable_id => @commentable.id.hex, :commentable_type => @commentable.class.name))
end
@comment.project = @project
@comment.user_id = current_user.id
if @comment.save
flash[:notice] = I18n.t("flash.comment.saved")
redirect_to commentable_path
redirect_to project_commentable_path(@project, @commentable)
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
@ -28,19 +19,12 @@ class CommentsController < ApplicationController
end
def edit
@update_url = case @commentable.class.name
when "Issue"
project_issue_comment_path(@project, @commentable, @comment)
when "Grit::Commit"
project_commit_comment_path(@project, @commentable, @comment)
end
@commentable_path = commentable_path
end
def update
if @comment.update_attributes(params[:comment])
flash[:notice] = I18n.t("flash.comment.saved")
redirect_to commentable_path
redirect_to project_commentable_path(@project, @commentable)
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
@ -49,30 +33,19 @@ class CommentsController < ApplicationController
def destroy
@comment.destroy
flash[:notice] = t("flash.comment.destroyed")
redirect_to commentable_path
redirect_to project_commentable_path(@project, @commentable)
end
private
protected
def set_commentable
@commentable = if params[:issue_id].present?
@project.issues.find_by_serial_id params[:issue_id]
elsif params[:commit_id].present?
@project.git_repository.commit params[:commit_id]
end
def find_commentable
@commentable = params[:issue_id].present? && @project.issues.find_by_serial_id(params[:issue_id]) ||
params[:commit_id].present? && @project.git_repository.commit(params[:commit_id])
end
def find_comment
@comment = Comment.find(params[:id])
if @comment.commit_comment?
@comment.project = @project
end
def find_or_build_comment
@comment = params[:id].present? && Comment.find(params[:id]) ||
current_user.comments.build(params[:comment]) {|c| c.commentable = @commentable; c.project = @project}
end
def commentable_path
@commentable.class == Issue ? [@project, @commentable] : commit_path(@project, @commentable.id)
end
end

View File

@ -1,3 +1,20 @@
# -*- encoding : utf-8 -*-
module CommentsHelper
def project_commentable_comment_path(project, commentable, comment)
case
when Comment.issue_comment?(commentable.class)
project_issue_comment_path(project, commentable, comment)
when Comment.commit_comment?(commentable.class)
project_commit_comment_path(project, commentable, comment)
end
end
def project_commentable_path(project, commentable)
case
when Comment.issue_comment?(commentable.class)
polymorphic_path [project, commentable]
when Comment.commit_comment?(commentable.class)
commit_path project, commentable.id
end
end
end

View File

@ -35,7 +35,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
end
when 'Comment'
if record.commentable.class == Issue
if record.issue_comment?
subscribes = record.commentable.subscribes
subscribes.each do |subscribe|
if record.user_id != subscribe.user_id

View File

@ -6,23 +6,45 @@ class Comment < ActiveRecord::Base
validates :body, :user_id, :commentable_id, :commentable_type, :project_id, :presence => true
scope :for_commit, lambda {|c| where(:commentable_id => c.id.hex, :commentable_type => c.class)}
default_scope order('created_at')
after_create :subscribe_on_reply, :unless => lambda {|c| c.commit_comment?}
after_create :subscribe_users
after_initialize do |comment|
class_eval { def commentable; project.git_repository.commit(commentable_id.to_s(16)); end } if commit_comment?
attr_accessible :body
def commentable
commit_comment? ? project.git_repository.commit(commentable_id.to_s(16)) : super
end
attr_accessible :body, :commentable_id, :commentable_type
attr_readonly :commentable_id, :commentable_type
def commentable=(c)
if self.class.commit_comment?(c.class)
self.commentable_id = c.id.hex
self.commentable_type = c.class.name
else
super
end
end
def own_comment?(user)
user_id == user.id
def self.commit_comment?(class_name)
class_name.to_s == 'Grit::Commit'
end
def commit_comment?
commentable_type == 'Grit::Commit'
self.class.commit_comment?(commentable_type)
end
def self.issue_comment?(class_name)
class_name.to_s == 'Issue'
end
def issue_comment?
self.class.issue_comment?(commentable_type)
end
def own_comment?(user)
user_id == user.id
end
def can_notify_on_new_comment?(subscribe)
@ -32,19 +54,19 @@ class Comment < ActiveRecord::Base
protected
def subscribe_on_reply
self.commentable.subscribes.create(:user_id => self.user_id) if !self.commentable.subscribes.exists?(:user_id => self.user_id)
commentable.subscribes.create(:user_id => user_id) if !commentable.subscribes.exists?(:user_id => user_id)
end
def subscribe_users
if self.commentable.class == Issue
self.commentable.subscribes.create(:user => self.user) if !self.commentable.subscribes.exists?(:user_id => self.user.id)
elsif self.commit_comment?
recipients = self.project.relations.by_role('admin').where(:object_type => 'User').map &:object # admins
recipients << self.user << User.where(:email => self.commentable.committer.email).first # commentor and committer
recipients << self.project.owner if self.project.owner_type == 'User' # project owner
if issue_comment?
commentable.subscribes.create(:user => user) if !commentable.subscribes.exists?(:user_id => user.id)
elsif commit_comment?
recipients = project.relations.by_role('admin').where(:object_type => 'User').map &:object # admins
recipients << user << User.where(:email => commentable.committer.email).first # commentor and committer
recipients << project.owner if project.owner_type == 'User' # project owner
recipients.compact.uniq.each do |user|
options = {:project_id => self.project.id, :subscribeable_id => self.commentable_id, :subscribeable_type => self.commentable.class.name, :user_id => user.id}
Subscribe.subscribe_to_commit(options) if Subscribe.subscribed_to_commit?(self.project, user, self.commentable)
options = {:project_id => project.id, :subscribeable_id => commentable_id, :subscribeable_type => commentable.class.name, :user_id => user.id}
Subscribe.subscribe_to_commit(options) if Subscribe.subscribed_to_commit?(project, user, commentable)
end
end
end

View File

@ -18,7 +18,7 @@ class Group < ActiveRecord::Base
scope :search_order, order("CHAR_LENGTH(uname) ASC")
scope :without, lambda {|a| where("groups.id NOT IN (?)", a)}
scope :search, lambda {|q| where("uname ILIKE ?", "%#{q.strip}%")}
scope :search, lambda {|q| where("uname ILIKE ?", "%#{q.to_s.strip}%")}
scope :opened, where('1=1')
scope :by_owner, lambda {|owner| where(:owner_id => owner.id)}
scope :by_admin, lambda {|admin| joins(:objects).where(:'relations.role' => 'admin', :'relations.object_id' => admin.id, :'relations.object_type' => 'User')}

View File

@ -29,7 +29,7 @@ class Platform < ActiveRecord::Base
after_update :update_owner_relation
scope :search_order, order("CHAR_LENGTH(name) ASC")
scope :search, lambda {|q| where("name ILIKE ?", "%#{q.strip}%")}
scope :search, lambda {|q| where("name ILIKE ?", "%#{q.to_s.strip}%")}
scope :by_visibilities, lambda {|v| where(:visibility => v)}
scope :opened, where(:visibility => 'open')
scope :hidden, where(:visibility => 'hidden')

View File

@ -29,7 +29,7 @@ class Project < ActiveRecord::Base
scope :recent, order("name ASC")
scope :search_order, order("CHAR_LENGTH(name) ASC")
scope :search, lambda {|q| by_name("%#{q.strip}%")}
scope :search, lambda {|q| by_name("%#{q.to_s.strip}%")}
scope :by_name, lambda {|name| where('projects.name ILIKE ?', name)}
scope :by_visibilities, lambda {|v| where(:visibility => v)}
scope :opened, where(:visibility => 'open')
@ -181,10 +181,6 @@ class Project < ActiveRecord::Base
system("#{Rails.root.join('bin', 'import_srpm.sh')} #{srpm_path} #{path} #{branch_name} >> /dev/null 2>&1")
end
def self.commit_comments(commit, project)
comments = Comment.where(:commentable_id => commit.id.hex, :commentable_type => 'Grit::Commit')
end
def owner?(user)
owner == user
end

View File

@ -49,7 +49,7 @@ class User < ActiveRecord::Base
scope :search_order, order("CHAR_LENGTH(uname) ASC")
scope :without, lambda {|a| where("users.id NOT IN (?)", a)}
scope :search, lambda {|q| where("uname ILIKE ?", "%#{q.strip}%")}
scope :search, lambda {|q| where("uname ILIKE ?", "%#{q.to_s.strip}%")}
scope :opened, where('1=1')
scope :banned, where(:role => 'banned')
scope :admin, where(:role => 'admin')

View File

@ -30,10 +30,10 @@ class CommentPresenter < ApplicationPresenter
def buttons
project = options[:project]
commentable = options[:commentable]
(ep, dp) = if commentable.class == Issue
(ep, dp) = if Comment.issue_comment?(commentable.class)
[edit_project_issue_comment_path(project, commentable, comment),
project_issue_comment_path(project, commentable, comment)]
elsif commentable.class == Grit::Commit
elsif Comment.commit_comment?(commentable.class)
[edit_project_commit_comment_path(project, commentable, comment),
project_commit_comment_path(project, commentable, comment)]
end

View File

@ -1,10 +1,10 @@
#open-comment.comment.view
%h3.tmargin0= t("layout.comments.new_header")
- if commentable.class == Issue
- if Comment.issue_comment?(commentable.class)
- new_path = project_issue_comments_path(project, commentable)
- is_subscribed = commentable.subscribes.exists?(:user_id => current_user.id)
- subscribe_path = is_subscribed ? project_issue_subscribe_path(project, commentable, current_user.id) : project_issue_subscribes_path(project, commentable)
- else commentable.class == Grit::Commit
- else Comment.commit_comment?(commentable.class)
- new_path = project_commit_comments_path(project, commentable)
- is_subscribed = Subscribe.subscribed_to_commit?(project, current_user, commentable)
- subscribe_path = is_subscribed ? unsubscribe_commit_path(project, commentable) : subscribe_commit_path(project, commentable)

View File

@ -1,14 +1,2 @@
.wrapper
= f.text_area :body, :cols => 80
.comment-right
= submit_tag t("layout.save")
-#.group
= f.label :body, t("activerecord.attributes.comment.body"), :class => :label
= f.text_area :body, :class => 'text_field', :cols => 80
-#.group.navform.wat-cf
%button.button{:type => "submit"}
= image_tag("choose.png", :alt => t("layout.save"))
= t("layout.save")
%span.text_button_padding= t("layout.or")
= link_to t("layout.cancel"), @commentable_path , :class => "text_button_padding link_button"
.wrapper= f.text_area :body, :cols => 80
.comment-right= submit_tag t("layout.save")

View File

@ -3,37 +3,4 @@
%h3= t("layout.comments.comments_header")
- list.each do |comment|
- CommentPresenter.present(comment, :project => project, :commentable => commentable) do |presenter|
= render :partial => 'shared/feed_message', :locals => {:presenter => presenter}
-#.block#block-list
.content
%h2.title
= t("layout.issues.comments_header")
.inner
%ul.list
- list.each do |comment|
%li
.left
= link_to comment.user.uname, user_path(comment.user.uname)
.item
= comment.body
%br
%br
- if commentable.class == Issue
- edit_path = edit_project_issue_comment_path(project, commentable, comment)
- delete_path = project_issue_comment_path(project, commentable, comment)
- elsif commentable.class == Grit::Commit
- edit_path = edit_project_commit_comment_path(project, commentable, comment)
- delete_path = project_commit_comment_path(project, commentable, comment)
= link_to t("layout.edit"), edit_path if can? :update, comment
=# link_to image_tag("x.png", :alt => t("layout.delete")) + " " + t("layout.delete"), delete_path, :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment
= link_to t("layout.delete"), delete_path, :method => "delete", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment
-#.block
.content
%h2.title
= t("layout.comments.new_header")
.inner
- new_path = project_issue_comments_path(project, commentable) if commentable.class == Issue
- new_path = project_commit_comments_path(project, commentable) if commentable.class == Grit::Commit
= form_for :comment, :url => new_path, :method => :post, :html => { :class => :form } do |f|
= render :partial => "comments/form", :locals => {:f => f}
= render :partial => 'shared/feed_message', :locals => {:presenter => presenter}

View File

@ -2,10 +2,10 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.comments.back"), @commentable_path
%li.first= link_to t("layout.comments.back"), project_commentable_path(@project, @commentable)
.content
%h2.title
= t("layout.comments.edit_header")
.inner
= form_for @comment, :url => @update_url, :html => { :class => :form } do |f|
= form_for @comment, :url => project_commentable_comment_path(@project, @commentable, @comment), :html => {:class => :form} do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -16,5 +16,5 @@
#repo-wrapper
= render :partial => 'show'
= render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project), :project => @project, :commentable => @commit}
= render :partial => "comments/list", :locals => {:list => Comment.for_commit(@commit), :project => @project, :commentable => @commit}
= render :partial => "comments/add", :locals => {:project => @project, :commentable => @commit} if current_user

View File

@ -1,6 +1,6 @@
%p== Hello, #{@user.user_appeal}.
- if @comment.commentable.class == Issue
- if @comment.issue_comment?
- link = link_to @comment.commentable.title, project_issue_url(@comment.commentable.project, @comment.commentable)
- object = 'issue'
- elsif @comment.commit_comment?

View File

@ -1,6 +1,6 @@
%p== Здравствуйте, #{@user.user_appeal}.
- if @comment.commentable.class == Issue
- if @comment.issue_comment?
- link = link_to @comment.commentable.title, project_issue_url(@comment.commentable.project, @comment.commentable)
- object = 'задаче'
- elsif @comment.commit_comment?

View File

@ -1,3 +1,4 @@
# -*- encoding : utf-8 -*-
class RemoveContainersAndRpms < ActiveRecord::Migration
def up
drop_table :containers

View File

@ -1,3 +1,4 @@
# -*- encoding : utf-8 -*-
class CustomizePlatform < ActiveRecord::Migration
def self.up
change_column_null :platforms, :name, false
@ -16,4 +17,4 @@ class CustomizePlatform < ActiveRecord::Migration
change_column_null :platforms, :visibility, true
remove_index "platforms", ["name"]
end
end
end

View File

@ -1,3 +1,4 @@
# -*- encoding : utf-8 -*-
class ClearProduct < ActiveRecord::Migration
def self.up
remove_column :products, :build_status
@ -10,4 +11,4 @@ class ClearProduct < ActiveRecord::Migration
add_column :products, :build_path, :string
add_column :products, :system_wide, :boolean, :default => false
end
end
end

View File

@ -11,14 +11,14 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120403110931) do
ActiveRecord::Schema.define(:version => 20120404134602) do
create_table "activity_feeds", :force => true do |t|
t.integer "user_id", :null => false
t.string "kind"
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "arches", :force => true do |t|
@ -102,7 +102,7 @@ ActiveRecord::Schema.define(:version => 20120403110931) do
t.string "locked_by"
t.datetime "created_at"
t.datetime "updated_at"
t.string "queue"
t.string "queue", :default => "default"
end
add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority"
@ -331,16 +331,16 @@ ActiveRecord::Schema.define(:version => 20120403110931) do
t.string "name"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
t.string "password_salt", :default => "", :null => false
t.string "reset_password_token"
t.datetime "remember_created_at"
t.datetime "created_at"
t.datetime "updated_at"
t.text "ssh_key"
t.string "uname"
t.string "role"
t.string "language", :default => "en"
t.integer "own_projects_count", :default => 0, :null => false
t.datetime "reset_password_sent_at"
t.integer "own_projects_count", :default => 0, :null => false
t.text "professional_experience"
t.string "site"
t.string "company"

View File

@ -1,323 +0,0 @@
КРАТКОЕ ОПИСАНИЕ ACL
====================
Предназначение
--------------
ACL предназначена для контроля прав пользователя на выполнение действий в
системе и доступа к моделям по областям видимости.
Решаемые задачи
---------------
* Проверка наличия у пользователя прав для выполнения метода контроллера;
* Прозрачная фильтрация моделей для исключения невидимых для пользователя
записей.
Возможности
-----------
* Неограниченное количество моделей, над которыми могут выполняться
действия (`target`);
* Неограниченное количество моделей, которые могут выполнять действия над
другими (`acter`);
* Геренатор прав основывающийся на структуре приложения (см. далее);
* Неограниченное количество ролей, которые могут назначаться для `acter` и
содержать любую комбинацию прав и доступных видимостей;
* Объединение прав `acter`-ов на глубину одной модели (см. далее);
* Разграничение назначения ролей по классам (не завершено, на данный
момент не критично);
* Разграничение ролей на глобальные и локальные (см. далее).
Типы моделей, с которыми взаимодействует ACL
--------------------------------------------
* __ActerModel__ -- модель, которая может выполнять действия, разрешенные
ролями, над другими моделями;
* __TargetTarget__ -- модель, над которой могут выполняться действия,
разрешенные ролями.
__ActerModel__ может иметь глобальную роль, которая определяет возможность
выполнения действий без привязки к конкретному экземпляру __TargetModel__ и
неограниченное количество прав по отношению к конкретному экземпляру
__TargetModel__.
__TODO__: *Реализовать дополнение необходимым функционалом моделей, выбранных
в качестве __ActerModel__ или __TargetModel__ при декларировании их роли в
системе*
Схема взаимодействия объектов ACL
---------------------------------
Функционал ACL реализуется путем взаимодействия моделей `Right, Role, Relation`,
реализующих основной функционал и особых моделей проекта, обозначенных на схеме
как `ActerModel` и `TargetModel`.
Экземпляры __ActerModel__ и __TargetModel__ связываются посредством модели
`Relation`, через которую экземпляр __ActerModel__ получает неограниченное
количество ролей по отношению к экземпляру __TargetModel__.
### Схема связей моделей:
--------------
| ActerModel |
/ --------------
--------- -------- |
| Right | | Role | V
--------- -------- ------------
... <= ... <= | Relation |
--------- -------- ------------
| Right | | Role | |
--------- -------- V
---------------
| TargetModel |
---------------
* Обозначения: <= -- Связь с несколькими моделями
<-,/,| -- Связь с одной моделью
Генератор прав
--------------
Генератор ролей является Rake-task-ом и запускается командой
`rake rights:generate`.
Желательно запускать после добавления нового метода в контроллер для того,
чтобы на этот метод в системе появилось право.
Загрузка ролей из дампа
-----------------------
Загрузку ролей из заранее подготовленного дампа можно произвести двумя
способами:
* В консоли, используя Rake task `rake roles:load`, который загрузит в базу
роли, описанные в `config/roles.yml`
* Через Web-интерфейс, на странице `/roles`, если у пользователя есть
соответствующие права. Для загрузки через Web-интерфейс необходимо выбрать
файл в поле выбора вверху страницы и нажать __Загрузить__.
Получение дампа ролей
---------------------
Дамп ролей может получить пользователь, имеющий на это права, зайдя на страницу
`roles` и нажав кнопку `Скачать в YML`.
Задание областей видимости моделей
----------------------------------
*Этот функционал скорее всего будет изменяться*
Если модель должна иметь несколько областей видимости, нужно сделать следующее:
* Добавить в модель константу `VISIBILITIES`, в которой задать названия областей
видимости;
* Добавить к таблице моделей поле `visibility:text`;
* Добавить `attr_accessible :visibility` в модель;
* Создать `scope :by_visibility`, принимающий аргументом массив областей
видимости.
После выполнения этих действий на странице редактирования роли появится поле
выбора областей видимости для этой модели.
### Пример:
model VisibilitiesExample < ActiveRecord::Base
VISIBILITIES = ['open', 'hidden', 'open_for_admins']
attr_accessible :visibility
scope :by_visibility, lambda {|v| {:conditions => ['visibility in (?)', v]}}
end
*Назначение методов описано в API*
Задание типа модели
-------------------
*Этот функционал скорее всего будет изменяться*
Если модель должна иметь возможность быть связанной с другими с использованием
ролей, необходимо произвести следующие действия:
* Добавить в модель декларацию `relationable`, с аргументом `:as`, который
может принимать заначения из `[:object, :target]`. Если модель будет
__acter__-ом, передается `:object`, иначе `:target`
Пример: `relationable :as => :object`
* Добавить в модель связь `belongs_to :global_role, :class_name => 'Role'`
* Добавить в модель связь с моделью `Relation`
* Если модель -- __acter__ и она должна использовать как свои роли, так и
роли из другой модели, необходимо добавить декларацию `inherit_rights_from`
которой аргументом присвоить имя/имена связей с моделями, из которых должны
браться роли.
### Примеры:
* Модель, являющаяся __acter__:
class ActerModel < ActiveRecord::Base
relationable :as => :object
belongs_to :global_role, :class_name => 'Role'
has_many :targets, :as => :object, :class_name => 'Relation'
end
* Модель, являющаяся __acter__ и наследующая права другой модели:
class ActerWithInheritableRolesModel < ActiveRecord::Base
relationable :as => :object
ingerit_rights_from :another_acter_model
has_many :another_acters_models
belongs_to :global_role, :class_name => 'Role'
has_many :targets, :as => :object, :class_name => 'Relation'
end
* Модель, являющаяся __target__:
class TargetModel < ActiveRecord::Base
relationable :as => :target
has_many :objects, :as => :target, :class_name => 'Relation'
end
* Модель, являющаяся и __acter__, и __target__:
class ActerAndTargetModel < ActiveRecord::Base
relationable :as => :object
relationable :as => :target
belongs_to :global_role, :class_name => 'Role'
has_many :targets, :as => :object, :class_name => 'Relation'
has_many :objects, :as => :target, :class_name => 'Relation'
end
*Назначение методов описано в API*
Использование ACL в контроллере
-------------------------------
Если необходимо ограничить доступ ко всем методам контроллера по глобальной
роли пользователя вне зависимости от текущей модели, необходимо установить
`before_filter :check_global_rights`.
В случае, если у пользователя нет прав для выполнения текущего действия, он
будет переотправлен на предыдущую страницу.
Если необходимо проверить, может ли пользователь выполнить конкретное действие,
необходимо в начале этого метода вызвать метод `can_perform?`. Если методу
передан параметр, являющийся экземпляром класса __TargetModel__, метод возвратит
`true`, если одна или несколько ролей пользователя над этой моделью позволяет
ему выполнить может выполнить действие и `false` в противном случае. Если
необязательный параметр опущен, или в качестве параметра передано `:system`,
учитываются только глобальные роли.
### Примеры
* Контроллер, некоторые методы которого доступны для всех:
class StuffController < ApplicationController
def index # доступ у всех
...
end
def show # 'Что-то полезное' выполнится только у тех, чьи роли над
# @data позволяют выполнить конкретное действие.
@data = Stuff.find(params[:id])
if can_perform? @data
#что-то полезное
else
# сообщаем пользователю, что он не может выполнить действие
end
end
def create # 'Что-то полезное' выполнится только у тех, чьи
# глобальные роли позволяют выполнить метод
if can_perform?
# что-то полезное
else
# сообщаем пользователю, что он не может выполнить действие
end
end
end
* Контроллер, доступ к методам которого возможен только при наличии необходимых
прав в глобальных ролях:
class StuffController < ApplicationController
before_filter :check_global_rights # разрешаем доступ только тем,
# чьи роли это позволяют.
def index # доступ только у тех, кому это позволяет глобальная роль
...
end
def show # 'Что-то полезное' выполнится только у тех, чьи роли
# над @data это позволяют
@data = Stuff.find(params[:id])
if can_perform? @data
#что-то полезное
else
# сообщаем пользователю, что он не может выполнить действие
end
end
end
Использование ACL во view
-------------------------
Используется метод `can_perform?` модели, для которой нужно проверить права
доступа. Обычно этой моделью является `current_user`.
### Примеры:
* Проверка на возможность выполнения глобального действия:
-if current_user.can_perform?('some_controller', 'some_aciton')
%a{:href => controller_action_path}= Some description
* Проверка на возможность выполнения действия над текущей моделью:
-if current_user.can_perform?('some_controller', 'some_aciton', @data)
%a{:href => controller_action_path(@data)}= Some description
API для работы с ACL
--------------------
*Этот функционал скорее всего будет изменяться*
### Методы потомков `ActiveRecord::Base`
*  Методы классов:
- `relationable` -- устанавливает, кем является модель (acter/target)
- `relationable?` -- может ли иметь связь с ролью/ролями с другими
- `relation_acters` -- список моделей, которые могут иметь роли
по отношению к другим (след. метод)
- `relation_targets` -- список моделей, над которыми могут совершаться
действия
- `relation_acter? (model)`, `relation_target? (model)` -- является ли
тем или другим
  - `inherit_rights_from (:relation_name | [:relation_names])` -- права из
каких связанных моделей наследовать
  - `visible_to (model)` -- все видимые для модели записи, может
включаться в цепочку (например, для paginate)
* Методы инстансов:
- `add_role_to(acter, role)` -- привязать acter-а с ролью к текущей записи
- `add_role_on(target, role)` -- привязать текущую модель с ролью
- `roles_to(object)` -- если object == :system, возвращает глобальные роли
текущей записи, если передана запись -- то роли текущей модели над записью
- `rights_to(object)` -- аргументы те же, но возвращается список прав,
собранный из всех ролей
- `right_to(controller_name, action)` -- возвращает запись с правом на
выполнение действия action в контроллере c именем `controller_name`
- `can_perform? (controller_name, action, target = :system)` -- показывает,
может ли текущая модель выполнить действие контроллера над целью
### Методы потомков `ActiveController::Base`
*Возможно, будут вынесены в хелпер для универсализации системы*
- `can_perform? (target = :system)` -- может ли `current_user` выполнить
текущее действие
- `check_global_access` -- делает редирект назад или на главную, если
пользователь вообще не может совершить текущее действие
- `roles_to(object)` -- возвращает список ролей `current_user`-а по отношению
к объекту
- `rights_to(object)` -- возвращает список прав `current_user`-а по отношению
к объекту

View File

@ -23,9 +23,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -43,9 +46,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -22,9 +22,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -42,9 +45,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -24,9 +24,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -44,9 +47,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -20,9 +20,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -40,9 +43,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -23,9 +23,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -43,9 +46,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -22,9 +22,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -42,9 +45,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,6 +17,9 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first active">Главная</a>
@ -37,9 +40,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -22,9 +22,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -42,9 +45,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -24,9 +24,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -44,9 +47,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -21,9 +21,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -41,9 +44,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -27,9 +27,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -47,9 +50,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -27,9 +27,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -47,9 +50,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -21,9 +21,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -41,9 +44,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->
@ -100,8 +100,14 @@
<th class="th3">Роль в проекте</th>
<th class="th4">Покинуть проект</th>
</tr>
</thead>
<tbody>
<tr class="search">
<td colspan="4">
<input type="text" value="Найти проект..." class="gray" onClick="if(this.value=='Найти проект...'){this.value='';this.className='black';}" onblur="if(this.value==''){this.value='Найти проект...';this.className='gray';}" />
</td>
</tr>
<tr id="Row1">
<td><a href="#"><div class="table-sort-left"><img src="pics/unlock.png" alt="unlock"></div><div class="table-sort-right">gsapronov/ROSA-Jabber</div></a></td>
<td class="td2">Проект создан для разработки интернет-пейджера, с простым

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -20,9 +20,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -40,9 +43,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -20,9 +20,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -40,9 +43,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -20,9 +20,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -40,9 +43,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -20,9 +20,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -40,9 +43,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -37,9 +37,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -57,9 +60,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->
@ -137,7 +137,7 @@
<article>
<div class="all">
<div class="desription-top">
<div class="description-top">
<div class="img">
<img src="pics/code.png" alt="pic" />
</div>

View File

@ -37,9 +37,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -57,9 +60,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->
@ -137,7 +137,7 @@
<article>
<div class="all">
<div class="desription-top">
<div class="description-top">
<input class="name" value="git@git.com:snigipini/wacomdrivers.git" />
<div class="role">
чтение и запись

View File

@ -17,9 +17,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first active">Главная</a>
<a href="#" class="active">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -37,9 +40,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -23,9 +23,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#">Проекты</a>
@ -43,9 +46,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -137,7 +137,7 @@
</aside>
<!--Right part of page-->
<div class="right">
<div class="desription-top">
<div class="description-top">
<div class="img">
<img src="pics/code.png" alt="pic" />
</div>

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,7 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

View File

@ -0,0 +1,217 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Сборочная среда</title>
<script type="text/javascript" src="js/html5shiv.js"></script>
<link rel="stylesheet" type="text/css" href="styles/main.css" />
<script type="text/javascript" src="js/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="js/all.js"></script>
<script type='text/javascript' src='js/checkbox-main.js'></script>
</head>
<body>
<div class="wrap columns">
<!--Top block-->
<header>
<div class="left">
</div>
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
</li>
<li>
<a href="#">Группы</a>
</li>
<li>
<a href="#">Мониторинг</a>
</li>
<li>
<a href="#">Платформа</a>
</li>
<li>
<a href="#">Документация</a>
</li>
</ul>
</menu>
<div class="information">
<!--Search-->
<div class="search">
<div class="pic">
</div>
<div class="field">
<input type="text" value="Поиск" class="gray" onClick="if(this.value=='Поиск'){this.value='';this.className='black';}" onblur="if(this.value==''){this.value='Поиск';this.className='gray';}" />
</div>
</div>
<div class="user">
<div class="avatar" onclick="droplist();">
<img src="pics/ava.png" alt="avatar" height="30" />
</div>
<div class="profile" onclick="droplist();">
<a href="#" onclick="droplist();">mikimaus <img src="pics/expand-white.png" alt="ex" /></a>
</div>
</div>
<div class="both">
</div>
<div class="droplist-wrap">
<div class="droplist" id="droplist">
<div class="a"><a href="#">Публичный профиль</a></div>
<div class="a"><a href="#">Настройки</a></div>
<div class="a"><a href="#">Выйти</a></div>
</div>
</div>
</div>
</div>
<div class="right">
</div>
<div class="both">
</div>
</header>
<!--Submenu-->
<div class="sub-menu">
<div class="left">
WacomDrivers
</div>
<nav>
<ul>
<li>
<a href="#">Проект</a>
</li>
<li>
<a href="#">Коммиты</a>
</li>
<li>
<a href="#">Сборки</a>
</li>
<li>
<a href="#">Трекер</a>
</li>
<li>
<a href="#" class="active">Wiki</a>
</li>
<li>
<a href="#">Readme</a>
</li>
<li>
<a href="#">Настройки</a>
</li>
</ul>
</nav>
</div>
<div class="both">
</div>
<!--Page-->
<article>
<!--Left part of page-->
<aside>
<div class="admin-preferences">
<ul>
<li>
<a href="#">Home</a>
</li>
<li class="active">
<a href="#">Pages</a>
</li>
<li>
<a href="#">Wiki history</a>
</li>
<li>
<a href="#">Git access</a>
</li>
</ul>
</div>
</aside>
<!--Right part of page-->
<div class="right">
<div class="left">
<h3>Home</h3>
</div>
<div class="r">
<a class="button" href="#">История</a>
</div>
<div class="both"></div>
<div id='wiki-content'>
<div class='has-footer has-rightbar wrap'>
<div class='gollum-markdown-content' id='wiki-body'>
<div id='template'><p>Lorem ipsum.</p>
<p>Lorem ipsum dolor sit amet.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sit amet ullamcorper arcu. Cras congue, velit sed sollicitudin dignissim, turpis erat ultrices risus, vitae gravida ligula elit vel urna. Curabitur mattis dapibus rhoncus. Aenean lorem nisi, lacinia sed elementum nec, consectetur ac augue. Quisque vulputate sodales nunc, et imperdiet ante tincidunt eu. Donec velit enim, dapibus at pharetra a, scelerisque ut sapien. Nullam luctus pulvinar dui nec convallis. Nullam viverra nisi nec orci fringilla varius.</p>
</div>
</div>
<div class='gollum-markdown-content' id='wiki-rightbar'>
<p>Это сайдбар.
Это сайдбар.
Это сайдбар.
Это сайдбар.
Это сайдбар.
Это сайдбар.
Это сайдбар.</p>
</div>
<div class='gollum-markdown-content' id='wiki-footer'>
<div id='footer-content'><p>Футер Футер Футер Футер</p></div>
</div>
</div>
</div>
<div id='gollum-footer'>
<p id='last-edit'>
Последним редактировал
<b><a href="/users/14">ironsnake</a></b>
около 19 часов
назад
</p>
</div>
<div class='both'></div>
</div>
<div class="both"></div>
</article>
</div>
<!--Footer-->
<footer>
<ul>
<li>
ROSA Лаб. © 2012 <img src="pics/square.png" alt="_" />
</li>
<li>
<img src="pics/flag.png" alt="rosa" /> <img src="pics/square.png" alt="_" />
</li>
<li>
<a href="#">О компании</a> <img src="pics/square.png" alt="_" />
</li>
<li>
<a href="#">Контакты</a> <img src="pics/square.png" alt="_" />
</li>
<li>
<a href="#">Условия использования</a> <img src="pics/square.png" alt="_" />
</li>
<li>
<a href="#">Конфиденциальность</a> <img src="pics/square.png" alt="_" />
</li>
<li>
<a href="#">Безопасность</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -18,9 +18,12 @@
<div class="middle">
<!--Main menu-->
<menu>
<div class="logo">
<a href="#"><img src="pics/logo-mini.png" alt="Главная" /></a>
</div>
<ul>
<li>
<a href="#" class="first">Главная</a>
<a href="#">Главная</a>
</li>
<li>
<a href="#" class="active">Проекты</a>
@ -38,9 +41,6 @@
<a href="#">Документация</a>
</li>
</ul>
<div class="logo">
<img src="pics/logo-mini.png" alt="logo" />
</div>
</menu>
<div class="information">
<!--Search-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
doc/design/pics/file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
doc/design/pics/group16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

BIN
doc/design/pics/group32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
doc/design/pics/user16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

BIN
doc/design/pics/user16g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

BIN
doc/design/pics/user32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

BIN
doc/design/pics/user32g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

126
doc/nginx/nginx.conf Normal file
View File

@ -0,0 +1,126 @@
user rosa;
worker_processes 2;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
accept_mutex on;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format ma§in '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
client_max_body_size 1G;
server_names_hash_bucket_size 64;
include conf.d/rosa_build.conf; # force default ip access
include conf.d/rosa_build_*;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443;
# server_name localhost;
# ssl on;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_timeout 5m;
# ssl_protocols SSLv2 SSLv3 TLSv1;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}

61
doc/nginx/rosa_build.conf Normal file
View File

@ -0,0 +1,61 @@
upstream rosa_build_backend {
# server 127.0.0.1:8080;
server unix:/tmp/rosa_build_unicorn.sock;
}
server {
listen 80;
server_name rosa-build.rosalab.ru;
root /srv/rosa_build/current/public;
if ($uri !~ downloads) {
rewrite ^(.*) https://$host$1 permanent;
}
location /downloads {
autoindex on;
}
access_log /srv/rosa_build/shared/log/nginx.access.log;
error_log /srv/rosa_build/shared/log/nginx.error.log;
#rewrite ^/downloads/(.*) http://$host/downloads/$1 break;
}
server {
listen 443 default ssl;
server_name 195.19.77.242;
root /srv/rosa_build/current/public;
ssl on;
ssl_certificate /etc/ssl/rosalinux.crt;
ssl_certificate_key /etc/ssl/rosalinux.key;
#ssl_verify_depth 3;
#location /downloads {
#autoindex on;
#}
try_files $uri/index.html $uri.html $uri @myapp;
location @myapp {
proxy_pass http://rosa_build_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
proxy_send_timeout 180;
proxy_redirect off;
}
rewrite ^/downloads/(.*) http://$host/downloads/$1 break;
#access_log /srv/rosa_build/shared/log/nginx.access.log;
#error_log /srv/rosa_build/shared/log/nginx.error.log;
error_page 500 502 503 504 /500.html;
location = /500.html {
root /srv/rosa_build/current/public;
}
}

View File

@ -83,9 +83,16 @@ describe BuildListsController do
end
context 'for guest' do
it 'should not be able to perform index action' do
get :index
response.should redirect_to(new_user_session_path)
if APP_CONFIG['anonymous_access']
it 'should be able to perform index action' do
get :index
response.should be_success
end
else
it 'should not be able to perform index action' do
get :index
response.should redirect_to(new_user_session_path)
end
end
end
@ -305,13 +312,16 @@ describe BuildListsController do
assigns[:build_lists].should_not include(@build_list2)
assigns[:build_lists].should include(@build_list3)
assigns[:build_lists].should_not include(@build_list4)
# response.should be_success
end
end
context 'callbacks' do
let(:build_list) { FactoryGirl.create(:build_list_core) }
before(:each) do
mock(controller).authenticate_build_service! {true}
end
describe 'publish_build' do
before { test_git_commit(build_list.project); build_list.update_attribute :commit_hash, build_list.project.git_repository.commits('master').last.id }

View File

@ -2,9 +2,7 @@
require 'spec_helper'
def create_comment user
comment = user.comments.create(:commentable_id => @commit.id.hex, :commentable_type => @commit.class.name,
:body => 'test', :project_id => @project.id)
comment
FactoryGirl.create(:comment, :user => user, :commentable => @commit, :project => @project)
end
shared_examples_for 'user with create comment rights for commits' do

View File

@ -39,7 +39,7 @@ end
shared_examples_for 'user without issue update rights' do
it 'should not be able to perform update action' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
response.should redirect_to(forbidden_path)
response.should redirect_to(controller.current_user ? forbidden_path : new_user_session_path)
end
it 'should not update issue title' do
@ -51,11 +51,11 @@ end
shared_examples_for 'user without issue destroy rights' do
it 'should not be able to perform destroy action' do
delete :destroy, :id => @issue.serial_id, :project_id => @project.id
response.should redirect_to(forbidden_path)
response.should redirect_to(controller.current_user ? forbidden_path : new_user_session_path)
end
it 'should not reduce issues count' do
lambda{ delete :destroy, :id => @issue.serial_id, :project_id => @project.id }.should change{ Issue.count }.by(0)
lambda{ delete :destroy, :id => @issue.serial_id, :project_id => @project.id }.should_not change{ Issue.count }
end
end
@ -185,4 +185,32 @@ describe IssuesController do
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
end
context 'for guest' do
if APP_CONFIG['anonymous_access']
it_should_behave_like 'issue user with project reader rights'
else
it 'should not be able to perform index action' do
get :index, :project_id => @project.id
response.should redirect_to(new_user_session_path)
end
it 'should not be able to perform show action' do
get :show, :project_id => @project.id, :id => @issue.serial_id
response.should redirect_to(new_user_session_path)
end
end
it 'should not be able to perform create action' do
post :create, @create_params
response.should redirect_to(new_user_session_path)
end
it 'should not create issue object into db' do
lambda{ post :create, @create_params }.should_not change{ Issue.count }
end
it_should_behave_like 'user without issue update rights'
it_should_behave_like 'user without issue destroy rights'
end
end

View File

@ -55,10 +55,17 @@ describe ProductBuildListsController do
response.should redirect_to(new_user_session_path)
end
it 'should not be able to view ProductBuildLists' do
get :index
response.should redirect_to(new_user_session_path)
end
if APP_CONFIG['anonymous_access']
it 'should be able to view ProductBuildLists' do
get :index
response.should be_success
end
else
it 'should not be able to view ProductBuildLists' do
get :index
response.should redirect_to(new_user_session_path)
end
end
end
context 'for user' do

View File

@ -0,0 +1,34 @@
# -*- encoding : utf-8 -*-
require 'spec_helper'
shared_examples_for 'able search' do
it 'should be able to search' do
get :index
response.should be_success
response.should render_template(:index)
end
end
shared_examples_for 'not able search' do
it 'should not be able to search' do
get :index
response.should redirect_to(controller.current_user ? forbidden_path : new_user_session_path)
end
end
describe SearchController do
before { stub_rsync_methods }
context 'as guest' do
if APP_CONFIG['anonymous_access']
it_should_behave_like 'able search'
else
it_should_behave_like 'not able search'
end
end
context 'as user' do
before {set_session_for FactoryGirl.create(:user)}
it_should_behave_like 'able search'
end
end

View File

@ -38,10 +38,6 @@ describe UsersController do
end
context 'with mass assignment' do
it 'should not be able to update uname' do
@simple_user.should_not allow_mass_assignment_of :uname
end
it 'should not be able to update role' do
@simple_user.should_not allow_mass_assignment_of :role
end

View File

@ -1,19 +1,18 @@
# -*- encoding : utf-8 -*-
FactoryGirl.define do
factory :platform do
description { FactoryGirl.generate(:string) }
name { FactoryGirl.generate(:unixname) }
description { FactoryGirl.generate(:string) }
platform_type 'main'
distrib_type APP_CONFIG['distr_types'].first
association :owner, :factory => :user
end
factory :platform_with_repos, :parent => :platform do
repositories {|r| [r.association(:repository)]}
factory :platform_with_repos do
after_create {|p| FactoryGirl.create_list(:repository, 1, platform: p)}
end
factory :personal_platform do
platform_type 'personal'
end
end
factory :personal_platform, :parent => :platform do
platform_type 'personal'
end
end

View File

@ -64,10 +64,6 @@ describe CanCan do
@ability.should_not be_able_to(:read, hidden_platform)
end
it 'should be able to auto build projects' do
@ability.should be_able_to(:auto_build, Project)
end
[:publish_build, :status_build, :pre_build, :post_build, :circle_build, :new_bbdt].each do |action|
it "should be able to #{ action } build list" do
@ability.should be_able_to(action, BuildList)
@ -90,7 +86,7 @@ describe CanCan do
@ability.should_not be_able_to(:destroy, register_request)
end
it 'should be able to register new user' do
pending 'should be able to register new user' do # while self registration is closed
@ability.should be_able_to(:create, User)
end
end

View File

@ -3,9 +3,7 @@ require 'spec_helper'
require "cancan/matchers"
def create_comment user
comment = user.comments.create(:commentable_id => @commit.id.hex, :commentable_type => @commit.class.name,
:body => 'test', :project_id => @project.id)
comment
FactoryGirl.create(:comment, :user => user, :commentable => @commit, :project => @project)
end
def set_comments_data_for_commit

View File

@ -24,4 +24,4 @@ describe ProductBuildList do
it { should allow_mass_assignment_of(:notified_at) }
it { should allow_mass_assignment_of(:base_url) }
end
end

View File

@ -31,4 +31,4 @@ describe Product do
FileUtils.rm_rf(APP_CONFIG['root_path'])
end
end
end