Attributy: id="railscast-189"
Poznámky k vysílání.
Příklad 47.28. User Model
class User < ActiveRecord::Base
acts_as_authentic
has_many :articles
has_many :comments
has_many :assignments
has_many :roles, :through => :assignments # many-to-many
def role_symbols
roles.map do |role|
role.name.underscore.to_sym
end
end
end
Příklad 47.29. authorization_rules.rb
authorization do role :admin do has_permission_on [:articles, :comments], :to => [:index, :show, :new, :create, :edit, :update, …] … end
Protože model/tabula Role je velmi úzce provázána s kódem a jakékoliv změny v tabulce musí být doplněny odpovídajícími změnami v kódu, ztrácí se úplně výhoda použítí datové tabulky. Následující postup je o tom jak tuto tabulku odstranit proměnit ve změny kódu. Nejdříve se zruší nepotřebné relace v modelu uživatele User
.
Příklad 47.30. User Model user.rb
class User < ActiveRecord::Base acts_as_authentic has_many :articles has_many :comments ROLES = %w[admin moderator author] def role_symbols [role.to_sym] end end
Pomocí migrací
$
script/generate migration add_role_to_users role:string$
rake db:migrate
Jak generátor pozná jak vytvořit migraci. Tedy opravdu rozpozná z názvu migrace že ten sloupec role má přidat do tabulky user?
Opravíme příslušné pohledy kde se zobrazují role.
Příklad 47.31. new_html.erb
… <p> <%= f.label :role %><br /> <%= f.collection_select :role, User::ROLES, :to_s, :humanize %> </p> …
Uvedený postup změní relaci mezi uživatelem a rolí na one-to-many
(jedna role, více uživatelů). Pokud chceme zachovat vztah many-to-many
, tedy že uživatel může mít přiřazeno více rolí, musíme to udělat jinak. Nejprve sloupeček role v databázi. Ten je třeba nahradit, protože už nám nestačí jedna role. Můžeme použít například sloupeček roles
do kterého „serializujeme“ pole/seznam s více rolemi.
Tento přístup přináší problémy při práci s uživateli, protože je třeba ošetřit sezam rolí, zakódovaný do sloupečku roles
. Ukázkou takového problému je, pokud budeme chtít vybrat z uživatelů například všechny kteří jsou administrátory.
Řešením tohoto problému je použití bitové masky. Výhodou je že uložená informace nezabírá mnoho místa, snadno se podle ní vyhledává. Bitovou masku reprezentujeme celým číslem (integer
)
$
script/generate migration add_roles_mask_to_users roles_mask:integer$
rake db:migrate
Upravíme model uživatele
Příklad 47.33. user.rb
… ROLES = %w[admin moderator author] def roles=(roles) self.roles_mask = (roles & ROLES).map {|r| 2**ROLES.index(r)}.sum end def roles ROLES.reject {|r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? } end def role_symbols roles.map(&:to_sym) end …
Upravíme pohledy
Příklad 47.34. new_html.erb
… <p> <%= f.label :roles %><br /> <% for role in User::ROLES %> <%= check_box_tag "user[roles][]", role, @user.roles.include?(role) > <%=h role.humanize ><br /> <% end %> <%= hidden_field_tag "user[roles][]", "" %> </p> …
Vyhledávání uživatelů s danou rolí upravíme v modelu
Příklad 47.35. user.rb
… named_scope :with_role, lambda {|role| {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0"} } …