has_many through

witam,

mam pewien problem z relacją has_many through i wyciąganiem danych.
mam 3 modele

user
coin
users_coins

[code]class User < ActiveRecord::Base

Include default devise modules. Others available are:

:token_authenticatable, :confirmable,

:lockable, :timeoutable and :omniauthable

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable

Setup accessible (or protected) attributes for your model

attr_accessible :email, :password, :password_confirmation, :remember_me

attr_accessible :title, :body

has_many :users_coins
has_many :coins, :through => :users_coins

end

class Coin < ActiveRecord::Base
attr_accessible :amount, :bought, :cost, :name
has_many :users_coins
has_many :users, :through => :users_coins
end

class UsersCoins < ActiveRecord::Base
attr_accessible :coin_id, :user_id
belongs_to :user
belongs_to :coin

end[/code]
tablela łącząca UsersCoins jest tylko po to aby logować każdy zakup coins których jest kilka rodzajów.

Teraz jak za pomocą current_user.id wyciągnąć wszystkie dane z UsersCoins oraz z Coins tak żeby wyświetlać to za jednym razem

Zakupy coins wyciągam sobie jak poniżej ale nie mam tutaj oprócz id danych co było kupione

@userscoins = UsersCoins.find_all_by_user_id (current_user.id)

znalazłem taki wątek ale szczerze mówiąc nie potrafię go przełożyć na swój kod

Z góry dzięki za pomoc

nie wiem czy do końca rozumiem, co próbujesz wyciągnąć (i czym dysponujesz na starcie), ale może

current_user.coins

Ci wystarczy?

no raczej to nie jest to.

dostaję błąd

uninitialized constant User::UsersCoin

a) Nazwij swoją klasę (tę joinowaną) UserCoin
b) has_many :user_coins
Wtedy powinno zadziałać.

Nie działa dlatego, że railsy próbują z nazwy asocjacji odtworzyć nazwę klasy, w której siedzi asocjowany model i spodziewają się (tak jak masz w backtrace) klasy UsersCoin, podczas gdy Ty ją nazwałeś nieco inaczej.

EDIT: A jeśli chcesz wiedzieć ogólniej, w jaki sposób railsy “tłumaczą” sobie różne nazwy (np. w routes.rb z get :users wiedzą, że powinny szukać klasy UsersController), to się pobaw tym:


:slight_smile:

Faktycznie, nie zauważyłem bałaganu w nazwach.
Jeśli chcesz iść wbrew konwencji z liczbą mnogą w nazwie modelu, to przy relacjach z tym modelem musisz dodawać opcję class_name.
Jeśli zrinejmujesz model, tak jak sugeruje Lypa, prawdopodobnie będziesz musiał dodać self.table_name=‘nazwa_twojej_tabeli’ do kodu tego modelu.

ok. przetestuje, nie jestem jeszcze aż taki oblatany w tych nazwach :slight_smile: ale wiem dlaczego tak zrobiłem :). Zasugerowałem się tym, oczywiście popełniając 2 błędy jeden to że to jest do relacji has_and_belongs_to_many a drugi nie zrobiłem nazwu tabeli w kolejności alfabetycznej

[quote=agile web development with rails (4th edition)]Rails implements many-to-many associations using an intermediate join table.
This contains foreign key pairs linking the two target tables. Active Record
assumes that this join table’s name is the concatenation of the two target
table names in alphabetical order[/quote]
i jeszcze pytanie na koniec. Według http://guides.rubyonrails.org/association_basics.html model łączący nie ma wymagań co do nazwy patrząc na przykład poniżej. Więc jak to wszystko ma się do mojego problemu bo szczerze mówiąc się pogubiłem :frowning:

[code]class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end

class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end

class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
end[/code]

No jak nie ma? Przecież wyraźnie masz model Manifest i has_many :manifests, gdzie manifests to liczba mnoga od “zunderscorowanego” Manifest. Gdybyś nazwał model Manifest jakoś inaczej, to ta asocjacja by nie działała.

ok. dzięki. to już widzę że będę miał z tymi nazwami problemy w przyszłości :slight_smile:

ok. to w związku z tym ze to zielona szkoła to zapytam jeszcze jak za jednym razem wyciągnąć dane z tych 2 tabel?

chodzi o to, że tabela joinowana jest logiem tranzakcji a druga przechowuje dane czego ta tranzakcja dotyczy.

teraz mam tak

[code] controller

@userscoins = current_user.coins

view

<% @userscoins.each do |mycoin| %> <% end %>[/code] a potrzebuję datę z tabeli joinowanej
current_user.user_coins.created_at

jak to zrobić najlepiej żeby było optymalnie i nie pytać x razy bazy?

<%= mycoin.name %> <%= mycoin.created_at %>

Ogólnie, do rozwiązywania problemu n+1 zapytań służy w ActiveRecord metoda includes(:association_name). Nie napiszę Ci, w jaki sposób masz jej użyć, bo zwyczajnie nie rozumiem, co chcesz zrobić. :slight_smile:

ok a możesz w takim razie napisać na swoim jakimś przykładzie jak za pomocą includes(:association_name) wyciągnąć jednocześnie dane z 2 tabeli tak aby wyświetlić je jedną pętlą?

Mamy:

[code=Ruby]class User < ActiveRecord::Base
has_many :posts
end

class Post < ActiveRecord::Base; end[/code]
I w kontrolerze, aby wyciągnąć użytkowników i ich posty, wystarczy wtedy napisać:

@users = User.includes(:posts)

.
Dzięki temu załadowane z bazy danych zostaną zarówno użytkownicy, jak i ich posty, do których dostęp uzyskujesz po prostu wpisujesz dla odpowiedniej instancji klasy User:

user.posts

dzięki, ale to mi raczej nie pomoże, ja mam asocjacje has_many :through i potrzebuję danych ze wszystkich tabel, tzn mam usera i chce wyciągnąć rekordy z 2 pozostałych, relacja działa jak najbatdziej ale nie wiem jak dostać się jednym zapytaniu także do danych z tabeli łączącej.

[code]@userscoins = current_user.coins

@userscoins = current_user.user.coins[/code]
potrzebuję wyciągnąc to w jednym zapytaniu

A czemu koniecznie w jednym? Tak jak napisałeś, to tutaj nie istnieje problem n+1 zapytań, zatem możesz spokojnie zrobić:

@user_coins = current_user.user_coins @coins = current_user.coins

[quote=patryk]dzięki, ale to mi raczej nie pomoże, ja mam asocjacje has_many :through i potrzebuję danych ze wszystkich tabel, tzn mam usera i chce wyciągnąć rekordy z 2 pozostałych, relacja działa jak najbatdziej ale nie wiem jak dostać się jednym zapytaniu także do danych z tabeli łączącej.

[code]@userscoins = current_user.coins

@userscoins = current_user.user.coins[/code]
potrzebuję wyciągnąc to w jednym zapytaniu[/quote]
@user_coins = current_user.user_coins.includes(:coins) (to i tak będą 2 query)

i teraz @user_coins.first.coin nie wywoła zapytania, tylko użyje association_cache

wielkie dzięki już mam :).

Stawiam browar :slight_smile: