47.27.13. Railscast Episode 189: Embedded Associations

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.

Příklad 47.32. user.rb

…
    serialize :roles
…

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"} }
…

Licence Creative Commons
Tento dokument Ruby, jehož autorem je Radek Hnilica, podléhá licenci Creative Commons Uveďte autora-Nevyužívejte dílo komerčně-Zachovejte licenci 3.0 Česká republika .