Commit 55893dbfd30682ea0de8cd780bed4cb5c320c48c

Authored by Rodrigo Souto
2 parents 9304a753 5f7895c3

Merge branch 'custom_roles_management'

app/controllers/admin/role_controller.rb
... ... @@ -2,7 +2,7 @@ class RoleController < AdminController
2 2 protect 'manage_environment_roles', :environment
3 3  
4 4 def index
5   - @roles = environment.roles.find(:all)
  5 + @roles = environment.roles.find(:all, :conditions => {:profile_id => nil})
6 6 end
7 7  
8 8 def new
... ...
app/controllers/my_profile/profile_members_controller.rb
... ... @@ -58,6 +58,7 @@ class ProfileMembersController < MyProfileController
58 58  
59 59 def change_role
60 60 @roles = Profile::Roles.organization_member_roles(environment.id)
  61 + @custom_roles = profile.custom_roles
61 62 begin
62 63 @member = profile.members.find(params[:id])
63 64 rescue ActiveRecord::RecordNotFound
... ...
app/controllers/my_profile/profile_roles_controller.rb 0 → 100644
... ... @@ -0,0 +1,115 @@
  1 +class ProfileRolesController < MyProfileController
  2 +
  3 + protect 'manage_custom_roles', :profile
  4 +
  5 + def index
  6 + @roles = profile.custom_roles
  7 + end
  8 +
  9 + def new
  10 + @role = Role.new
  11 + end
  12 +
  13 + def create
  14 + @role = Role.create({:name => params[:role][:name], :permissions => params[:role][:permissions], :profile_id => profile.id, :environment => environment }, :without_protection => true)
  15 + if @role.save
  16 + redirect_to :action => 'show', :id => @role
  17 + else
  18 + session[:notice] = _('Failed to create role')
  19 + render :action => 'new'
  20 + end
  21 + end
  22 +
  23 + def show
  24 + @role = environment.roles.find(params[:id])
  25 + end
  26 +
  27 + def edit
  28 + @role = environment.roles.find(params[:id])
  29 + end
  30 +
  31 + def assign_role_by_members
  32 + return redirect_to "/" if params[:q].nil? or !request.xhr?
  33 + arg = params[:q].downcase
  34 + result = find_by_contents(:people, environment, profile.members, params[:q])[:results]
  35 + render :text => prepare_to_token_input(result).to_json
  36 + end
  37 +
  38 + def destroy
  39 + @role = environment.roles.find(params[:id])
  40 + @members = profile.members_by_role(@role)
  41 + @roles_list = all_roles(environment, profile)
  42 + @roles_list.delete(@role)
  43 + end
  44 +
  45 + def remove
  46 + @role = environment.roles.find(params[:id])
  47 + @members = profile.members_by_role(@role)
  48 + member_roles = params[:roles] ? environment.roles.find(params[:roles].select{|r|!r.to_i.zero?}) : []
  49 + append_roles(@members, member_roles, profile)
  50 + if @role.destroy
  51 + session[:notice] = _('Role successfuly removed!')
  52 + else
  53 + session[:notice] = _('Failed to remove role!')
  54 + end
  55 + redirect_to :action => 'index'
  56 + end
  57 +
  58 + def update
  59 + @role = environment.roles.find(params[:id])
  60 + if @role.update_attributes(params[:role])
  61 + redirect_to :action => 'show', :id => @role
  62 + else
  63 + session[:notice] = _('Failed to edit role')
  64 + render :action => 'edit'
  65 + end
  66 + end
  67 +
  68 + def assign
  69 + @role = environment.roles.find(params[:id])
  70 + @roles_list = all_roles(environment, profile)
  71 + @roles_list.delete(@role)
  72 + end
  73 +
  74 + def define
  75 + @role = environment.roles.find(params[:id])
  76 + selected_role = params[:selected_role] ? environment.roles.find(params[:selected_role].to_i) : nil
  77 + if params[:assign_role_by].eql? "members"
  78 + members_list = params[:person_id].split(',').collect {|id| environment.profiles.find(id.to_i)}
  79 + members_list.collect{|person| person.add_role(@role, profile)}
  80 + elsif params[:assign_role_by].eql? "roles"
  81 + members = profile.members_by_role(selected_role)
  82 + replace_role(members, selected_role, @role, profile)
  83 + else
  84 + session[:notice] = _("Error")
  85 + end
  86 + redirect_to :action => 'index'
  87 + end
  88 +
  89 + protected
  90 +
  91 + def append_roles(members, roles, profile)
  92 + members.each do |person|
  93 + all_roles = person.find_roles(profile).map(&:role) + roles
  94 + person.define_roles(all_roles, profile)
  95 + end
  96 + end
  97 +
  98 + def all_roles(environment, profile)
  99 + Profile::Roles.organization_member_roles(environment.id) + profile.custom_roles
  100 + end
  101 +
  102 + def replace_roles(members, roles, profile)
  103 + members.each do |person|
  104 + person.define_roles(roles, profile)
  105 + end
  106 + end
  107 +
  108 + def replace_role(members, role, new_role, profile)
  109 + members.each do |person|
  110 + person.remove_role(role, profile)
  111 + person.add_role(new_role, profile)
  112 + end
  113 + end
  114 +
  115 +end
... ...
app/models/organization.rb
... ... @@ -29,6 +29,8 @@ class Organization &lt; Profile
29 29  
30 30 has_many :mailings, :class_name => 'OrganizationMailing', :foreign_key => :source_id, :as => 'source'
31 31  
  32 + has_many :custom_roles, :class_name => 'Role', :foreign_key => :profile_id
  33 +
32 34 scope :more_popular, :order => 'members_count DESC'
33 35  
34 36 validate :presence_of_required_fieds, :unless => :is_template
... ...
app/models/profile.rb
... ... @@ -43,7 +43,7 @@ class Profile &lt; ActiveRecord::Base
43 43 find_role('editor', env_id)
44 44 end
45 45 def self.organization_member_roles(env_id)
46   - all_roles(env_id).select{ |r| r.key.match(/^profile_/) unless r.key.blank? }
  46 + all_roles(env_id).select{ |r| r.key.match(/^profile_/) unless r.key.blank? || !r.profile_id.nil?}
47 47 end
48 48 def self.all_roles(env_id)
49 49 Role.all :conditions => { :environment_id => env_id }
... ... @@ -75,6 +75,7 @@ class Profile &lt; ActiveRecord::Base
75 75 'publish_content' => N_('Publish content'),
76 76 'invite_members' => N_('Invite members'),
77 77 'send_mail_to_members' => N_('Send e-Mail to members'),
  78 + 'manage_custom_roles' => N_('Manage custom roles'),
78 79 }
79 80  
80 81 acts_as_accessible
... ...
app/views/profile_editor/index.html.erb
... ... @@ -28,6 +28,8 @@
28 28  
29 29 <%= control_panel_button(_('Manage Content'), 'cms', :controller => 'cms') %>
30 30  
  31 + <%= control_panel_button(_('Manage Roles'), 'roles', :controller => 'profile_roles') %>
  32 +
31 33 <% unless profile.enterprise? %>
32 34 <%= case profile.blogs.count
33 35 when 0
... ...
app/views/profile_members/change_role.html.erb
1 1 <h3> <%= _('Changing role of %s') % @member.name %> </h3>
2 2  
3 3 <%= labelled_form_for :member, :url => {:action => 'update_roles'} do |f| %>
4   -
5   - <%= _('Roles:') %> <br>
  4 +
  5 + <h4><%= _('Roles:') %></h4>
6 6 <% @roles.each do |r| %>
7 7 <%= labelled_check_box(r.name, 'roles[]', r.id, @associations.map(&:role).include?(r) ) %><br/>
8 8 <ul class="role-permissions">
... ... @@ -11,6 +11,17 @@
11 11 <% end %>
12 12 </ul>
13 13 <% end %>
  14 + <% unless @custom_roles.empty? %>
  15 + <h4><%= _('Custom Roles:') %></h4>
  16 + <% @custom_roles.each do |r| %>
  17 + <%= labelled_check_box(r.name, 'roles[]', r.id, @associations.map(&:role).include?(r) ) %><br/>
  18 + <ul class="role-permissions">
  19 + <% r.permissions.each do |p| %>
  20 + <li> <%= permission_name(p) %> </li>
  21 + <% end %>
  22 + </ul>
  23 + <% end %>
  24 + <% end %>
14 25 <%= hidden_field_tag 'person', @member.id %>
15 26  
16 27 <% button_bar do %>
... ...
app/views/profile_roles/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<%= error_messages_for :role %>
  2 +
  3 +<%= labelled_form_for :role, :url => (mode == :edit) ? {:action => 'update', :id => role} : {:action => 'create'} do |f| %>
  4 +
  5 + <%= required_fields_message %>
  6 +
  7 + <%= required f.text_field(:name) %>
  8 +
  9 + <% permissions.each do |key| %>
  10 + <div class="permissions <%= key.downcase %>">
  11 + <h4><%= _('%s Permissions:' % key) %></h4>
  12 + <% ActiveRecord::Base::PERMISSIONS[key].keys.each do |p| %>
  13 + <%= check_box_tag("role[permissions][]", p, role.has_permission?(p), { :id => p }) %>
  14 + <%= content_tag(:label, permission_name(p), { :for => p }) %><br/>
  15 + <% end %>
  16 + </div>
  17 + <% end %>
  18 +
  19 + <% button_bar do %>
  20 + <%= submit_button('save', (mode == :edit) ? _('Save changes') : _('Create role'), :cancel => {:action => 'index'} ) %>
  21 + <% end %>
  22 +<% end %>
... ...
app/views/profile_roles/assign.html.erb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +<%= javascript_include_tag('assign_role.js') %>
  2 +
  3 +<h1> <%= _("Assign #{@role.name}") %> </h1>
  4 +
  5 +
  6 +<%= labelled_form_for :role, :url => { :action => 'define', :id => @role.id } do |f| %>
  7 +
  8 + <h2>
  9 + <%= _("Assign role by:") %>
  10 + </h2>
  11 + <p>
  12 + <%= labelled_radio_button _("Members"), :assign_role_by, "members", true, :id => "assign_role_by_members", :class => "assign_role_by" %>
  13 + &nbsp;
  14 + <%= labelled_radio_button _("Roles"), :assign_role_by, "roles", false, :id => "assign_role_by_roles", :class => "assign_role_by" %>
  15 + </p>
  16 + <div class="assign_by_members">
  17 + <%=token_input_field_tag(:person_id, 'search-profile-members', {:action => 'assign_role_by_members'},
  18 + {:focus => false, :hint_text => _('Select members to assign the role')}) %>
  19 +
  20 + <% button_bar do %>
  21 + <%= submit_button(:forward, _("Confirm")) %>
  22 + <% end %>
  23 + </div>
  24 + <div class="assign_by_roles" style="display: none;">
  25 + <h6>
  26 + <%= _("Replace role: ") %>
  27 + </h6>
  28 + <% @roles_list.each do |role| %>
  29 + <%= labelled_radio_button role.name , :selected_role, role.id , false, :class => "selected_role" %> <br>
  30 + <% end %>
  31 + <% button_bar do %>
  32 + <%= submit_button('save',_('Confirm'), :cancel => {:action => 'index'} ) %>
  33 + <% end %>
  34 + </div>
  35 +<% end %>
... ...
app/views/profile_roles/destroy.html.erb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +<h1> <%= _("Deleting #{@role.name}") %> </h1>
  2 +
  3 +<% if @members.nil? || @members.empty? %>
  4 + <p><%= _('This role is not being currently used.')%></p>
  5 + <p><%= _('Are you sure you want to delete this role?') %></p>
  6 +
  7 + <% button_bar do %>
  8 + <%= button(:remove, _('Yes, I am sure'), {:action => 'remove', :id => @role.id}, :method => :post) %>
  9 + <%= button(:cancel, _('No, I gave up'), {:action => 'index'}) %>
  10 + <% end %>
  11 +<% else %>
  12 + <p><%= _('There are members currently using this role.')%></p>
  13 + <p><%= _('To which role do you want to change them?') %></p>
  14 + <%= labelled_form_for :role, :url => { :action => 'remove', :id => @role.id } do |f| %>
  15 + <% @roles_list.each do |role| %>
  16 + <%= check_box_tag("roles[]", role.id, false ,{:id => role.key}) %>
  17 + <%= content_tag(:label, role.name, { :for => role.key }) %><br/>
  18 + <% end %>
  19 + <% button_bar do %>
  20 + <%= submit_button('save',_('Delete role'), :cancel => {:action => 'index'} ) %>
  21 + <% end %>
  22 + <% end %>
  23 +<% end %>
... ...
app/views/profile_roles/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1> <%= _("Editing #{@role.name}") %> </h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :edit, :role => @role, :permissions => [@role.kind] } %>
... ...
app/views/profile_roles/index.html.erb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<h1><%= _('Manage user roles') %></h1>
  2 +
  3 +<table>
  4 + <tr>
  5 + <th><%= _('Role') %></th>
  6 + <th><%= _('Actions') %></th>
  7 + </tr>
  8 + <% @roles.each do |role| %>
  9 + <tr>
  10 + <td>
  11 + <%= link_to role.name, :action => 'show', :id => role %>
  12 + </td>
  13 + <td>
  14 + <div style="text-align: center;">
  15 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => role %>
  16 + <%= button_without_text :delete, _('Delete'), :action => 'destroy', :id => role %>
  17 + <%= button_without_text 'vertical-toggle', _('Assign'), :action => 'assign', :id => role %>
  18 + </div>
  19 + </td>
  20 + </tr>
  21 + <% end %>
  22 +</table>
  23 +
  24 +<% button_bar do %>
  25 + <%= button :add, _('Create a new role'), :action => 'new' %>
  26 + <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %>
  27 +<% end %>
... ...
app/views/profile_roles/new.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1> <%= _("Create a new role") %> </h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :create, :role => @role, :permissions => ['Profile'] } %>
... ...
app/views/profile_roles/show.html.erb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<h1> <%= _(@role.name) %></h1>
  2 +
  3 +<h3> <%= _('Permissions') %> </h3>
  4 +<ul>
  5 + <% @role.permissions.each do |p| %>
  6 + <li> <%= permission_name(p) %> </li>
  7 + <% end %>
  8 +</ul>
  9 +
  10 +<% button_bar do %>
  11 + <%= button :edit, _('Edit'), :action => 'edit', :id => @role %>
  12 + <%= button :back, _('Back to roles management'), :action => 'index' %>
  13 +<% end %>
... ...
app/views/tasks/_add_member_accept_details.html.erb
1 1 <%= content = _("Roles:")+"<br />"
2   -roles = Profile::Roles.organization_member_roles(task.target.environment.id)
  2 +roles = Profile::Roles.organization_all_roles(task.target.environment.id)
3 3 roles.each do |role|
4 4 content += labelled_check_box(role.name, "tasks[#{task.id}][task][roles][]", role.id, false)+"<br />"
5 5 end
6 6 content_tag('p', content, :class => 'member-classify-suggestion')
7 7 %>
8   -
... ...
db/migrate/20150203143051_add_reference_to_role.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class AddReferenceToRole < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :roles, :profile_id, :integer
  4 + end
  5 + def self.down
  6 + remove_column :roles , :profile_id
  7 + end
  8 +end
... ...
db/migrate/20150210143723_add_custom_roles_permission_to_admin_roles.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +class AddCustomRolesPermissionToAdminRoles < ActiveRecord::Migration
  2 + def self.up
  3 + environment_admin = Role.find_by_key("environment_administrator")
  4 + profile_admin = Role.find_by_key("profile_admin")
  5 + environment_admin.permissions.append("manage_custom_roles")
  6 + profile_admin.permissions.append("manage_custom_roles")
  7 + environment_admin.save!
  8 + profile_admin.save!
  9 + end
  10 + def self.down
  11 + environment_admin = Role.find_by_key("environment_administrator")
  12 + profile_admin = Role.find_by_key("profile_admin")
  13 + environment_admin.permissions.delete("manage_custom_roles")
  14 + profile_admin.permissions.delete("manage_custom_roles")
  15 + environment_admin.save!
  16 + profile_admin.save!
  17 + end
  18 +end
... ...
public/images/control-panel/role-management.gif 0 → 100644

2.07 KB

public/images/control-panel/role-management.png 0 → 100644

3.63 KB

public/javascripts/assign_role.js 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +(function($){
  2 + 'use strict';
  3 +
  4 + function toggle_assignment_method() {
  5 + if (this.value != "roles") {
  6 + $('.assign_by_roles').hide();
  7 + $('.assign_by_members').show();
  8 + } else {
  9 + $('.assign_by_members').hide();
  10 + $('.assign_by_roles').show();
  11 + }
  12 + }
  13 +
  14 + $(document).ready(function() {
  15 + $('.assign_by_roles').hide();
  16 + // Event triggers
  17 + $('.assign_role_by').click(toggle_assignment_method);
  18 + });
  19 +})(jQuery);
... ...
public/stylesheets/application.css
... ... @@ -4685,6 +4685,12 @@ h1#agenda-title {
4685 4685 .controller-profile_editor a.control-panel-welcome-page {
4686 4686 background-image: url(../images/control-panel/welcome-page.png)
4687 4687 }
  4688 +.controller-profile_editor a.control-panel-roles {
  4689 + background-image: url(../images/control-panel/role-management.png)
  4690 +}
  4691 +.controller-profile_editor .msie6 a.control-panel-roles {
  4692 + background-image: url(../images/control-panel/role-management.gif)
  4693 +}
4688 4694 /* ==> public/stylesheets/controller_profile_members.css <== */
4689 4695 .controller-profile_members .no-boxes {
4690 4696 margin: 30px
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -175,7 +175,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
175 175 admin = fast_create(Person)
176 176 community.add_member(admin)
177 177  
178   - folder = fast_create(Folder, :profile_id => community.id, :published => false)
  178 + folder = fast_create(Folder, :profile_id => community.id, :published => false, :show_to_followers => false)
179 179 community.add_member(profile)
180 180 login_as(profile.identifier)
181 181  
... ... @@ -278,7 +278,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
278 278 should 'not give access to private articles if logged in and only member' do
279 279 person = create_user('test_user').person
280 280 profile = Profile.create!(:name => 'test profile', :identifier => 'test_profile')
281   - intranet = Folder.create!(:name => 'my_intranet', :profile => profile, :published => false)
  281 + intranet = Folder.create!(:name => 'my_intranet', :profile => profile, :published => false, :show_to_followers => false)
282 282 profile.affiliate(person, Profile::Roles.member(profile.environment.id))
283 283 login_as('test_user')
284 284  
... ...
test/functional/profile_roles_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,106 @@
  1 +require_relative "../test_helper"
  2 +require 'profile_roles_controller'
  3 +
  4 +class ProfileRolesControllerTest < ActionController::TestCase
  5 +
  6 + def setup
  7 + @controller = ProfileRolesController.new
  8 + @request = ActionController::TestRequest.new
  9 + @response = ActionController::TestResponse.new
  10 + @role = Role.find(:first)
  11 + end
  12 +
  13 + should 'create a custom role' do
  14 + community = fast_create(Community)
  15 + admin = create_user_with_permission('admin_user', 'manage_custom_roles', community)
  16 + login_as :admin_user
  17 + post :create, :profile => community.identifier, :role => {:name => "some_role", :permissions => ["edit_profile"] }
  18 + role = Role.where(:name => 'some_role').first
  19 +
  20 + assert_not_nil role
  21 + assert_equal community.id, role.profile_id
  22 + end
  23 +
  24 + should 'not create a custom role without permission' do
  25 + community = fast_create(Community)
  26 + moderator = create_user_with_permission('profile_admin', 'edit_profile', community)
  27 + login_as :profile_admin
  28 + post :create, :profile => community.identifier, :role => {:name => "new_admin", :permissions => ["edit_profile"] }
  29 +
  30 + assert_response 403
  31 + assert_template 'access_denied'
  32 +
  33 + role = Role.where(:name => 'new_admin')
  34 +
  35 + assert_empty role
  36 + end
  37 +
  38 +
  39 + should 'delete a custom role not used' do
  40 + community = fast_create(Community)
  41 + admin = create_user_with_permission('admin_user', 'manage_custom_roles', community)
  42 + login_as :admin_user
  43 + role = Role.create!({:name => 'delete_article', :key => 'profile_delete_article', :profile_id => community.id, :environment => Environment.default}, :without_protection => true)
  44 + post :remove , :profile => community.identifier, :id => role.id
  45 +
  46 + assert_response :redirect
  47 + assert_redirected_to :action => 'index'
  48 +
  49 + assert_not_includes Role.all, role
  50 + end
  51 +
  52 + should 'delete a custom role being used' do
  53 + community = fast_create(Community)
  54 + admin = create_user_with_permission('admin_user', 'manage_custom_roles', community)
  55 + login_as :admin_user
  56 + role = Role.create!({:name => 'delete_article', :key => 'profile_delete_article', :profile_id => community.id, :environment => Environment.default}, :without_protection => true)
  57 + admin.add_role(role, community)
  58 + moderator_role = Role.find_by_name("moderator")
  59 +
  60 + assert_not_includes community.members_by_role(moderator_role), admin
  61 +
  62 + post :remove , :profile => community.identifier, :id => role.id, :roles => [moderator_role.id]
  63 +
  64 + assert_response :redirect
  65 + assert_redirected_to :action => 'index'
  66 +
  67 + assert_not_includes Role.all, role
  68 + assert_includes community.members_by_role(moderator_role), admin
  69 + end
  70 +
  71 + should 'assign a custom role to single user' do
  72 + community = fast_create(Community)
  73 + admin = create_user_with_permission('admin_user', 'manage_custom_roles', community)
  74 + login_as :admin_user
  75 + role = Role.create!({:name => 'delete_article', :key => 'profile_delete_article', :profile_id => community.id, :environment => Environment.default}, :without_protection => true)
  76 +
  77 + assert_not_includes community.members_by_role(role), admin
  78 +
  79 + post :define, :profile => community.identifier, :id => role.id, :assign_role_by => "members", :person_id => admin.id
  80 +
  81 + assert_includes community.members_by_role(role), admin
  82 + end
  83 +
  84 + should 'replace a role with a custom role' do
  85 + community = fast_create(Community)
  86 + admin = create_user_with_permission('admin_user', 'manage_custom_roles', community)
  87 + moderator = create_user_with_permission('profile_admin', 'edit_profile', community)
  88 + login_as :admin_user
  89 + role = Role.create!({:name => 'delete_article', :key => 'profile_delete_article', :profile_id => community.id, :environment => Environment.default}, :without_protection => true)
  90 + moderator_role = Role.find_by_name("moderator")
  91 + admin.add_role(moderator_role, community)
  92 +
  93 + assert_not_includes community.members_by_role(role), admin
  94 +
  95 + assert_not_includes community.members_by_role(role), moderator
  96 + assert_not_includes community.members_by_role(moderator_role), moderator
  97 +
  98 + post :define, :profile => community.identifier, :id => role.id, :assign_role_by => "roles", :selected_role => moderator_role.id
  99 +
  100 + assert_not_includes community.members_by_role(moderator_role), admin
  101 + assert_includes community.members_by_role(role), admin
  102 +
  103 + assert_not_includes community.members_by_role(role), moderator
  104 + assert_not_includes community.members_by_role(moderator_role), moderator
  105 + end
  106 +end
... ...
vendor/plugins/access_control/lib/role.rb
... ... @@ -4,6 +4,7 @@ class Role &lt; ActiveRecord::Base
4 4  
5 5 has_many :role_assignments, :dependent => :destroy
6 6 belongs_to :environment
  7 + belongs_to :organization
7 8 serialize :permissions, Array
8 9 validates_presence_of :name
9 10 validates_uniqueness_of :name, :scope => :environment_id
... ...