Podwójne has_many

Mam taki problem szkolny.

Chciałbym stworzyć dwa modele: Subject i Class.

W przedmiotach (Subject) będę przechowywał nazwy przedmiotów: polski, matma, biologia itd.
W klasach (Class) będę przechowywał opisy klas: np. matematyczno-informatyczno-fizyczna, biologiczno-chemiczna itd.

W klasach (Class) chciałbym mieć informację o przedmiotach punktowanych podczas rekrutacji (dla każdej klasy inne przedmioty), zatem chciałbym stworzyć relację:

Subject has_many Class
Class has_many Subject

czyli relacja N:N

Chciałbym żeby w widoku dla klasy (Class) można było wybierać jeden lub dwa przedmioty punktowane z listy wyboru.
Mój pomysł jest taki żeby to zrobić przez :through

Ale ponieważ można wybrać max. 2 przedmioty punktowane to może dało by się to zrobić prościej ?
Np.

rails g scaffold class nazwa_klasy:string opis:text subject1_id:integer subject2_id:integer

To brzmi jak relacja has_and_belongs_to_many z walidacja liczby przedmiotow

W 99% przypadków zamiast has_and_belongs_to_many lepiej użyć dwóch has_many :through.

Relacja has_and_belongs_to_many w moim przypadku byłaby OK.

Ale tak jak pisałem max 2 przedmioty mogę wybrać dla klasy.
Więc model Class (klasa) może mieć na sztywno zdefiniowane dwa klucze obce

CLASS Model
nazwa_klasy
opis
subject1_id
subject2_id

tylko jak zapisać relacje w modelach ?

Może wykorzystać :foreign_key dla has_many

Ale po co tak kombinować? Przecież Convention over configuration :wink: Teoretycznie habtm da radę, ale w zupełności zgadzam się z sharnikiem, że has_many :through jest najczęściej dużo lepszym wyborem. W tym przypadku również - mając trzeci model, dysponujesz walidacjami, callbackami czy dodatkowymi atrybutami dla każdej pary obiektów Subject i Class. W ten sposób możesz na przykład bardzo łatwo sprawdzać, czy dana klasa ma już dwa przypisane przedmioty (poczytaj przy okazji o counter cache).

Btw. nie wiem, czy moje wątpliwości są zasadne, musiałby to ktoś bardziej ogarnięty wyjaśnić, ale wydaje mi się, że nazywanie klasy Class nie jest najszczęśliwsze (vide http://apidock.com/rails/Class)

Nazywajac cos Class otworzysz klase ktora juz istnieje pozatym wyglad takiego potworka przyprawia o bol glowy

class Class end

No więc właśnie :wink:

Nawet raisl g scaffold class … zgłosił zarezerwowane słowo, ale jak jest ‘oddział klasowy’ po angielsku ? :slight_smile:

Może Course?

Stworzyłem już sobie kilka modele (przez rails g scaffold)

Mam między innymi modele
Candidate i Candidate_profile
Candidate has_many Candidate_profiles.

W widoku Candidates dla akcji index dodałem taki link:

<%= link_to 'Wybór klas', candidate_profiles_path, :candidate_id => candidate.id %>

W kontrolerze CanididateProfilesController mam coś takiego

def index @candidate_profiles = CandidateProfile.where("candidate_id = ?", params[:candidate_id]) end
I w konsoli dostaje coś takiego:
SELECT “candidate_profiles”.* FROM “candidate_profiles” WHERE (candidate_id = NULL)

Co robię źle? Da się to zrobić bez WHERE, tak żeby nie używać SQL’a ?

A co właściwie chcesz zrobić? Wyświetlić wszystkie? W takim razie:

@candidate_profiles = CandidateProfile.all

Nie chcę wyświetlić wszystkich.

Tak jak pisałem candidate has_many candidate_profiles

W tabeli candidate_profiles jest klucz obcy candidate_id.

Chcę wyświetlić tylko profile należące do konkretnego kandydata

Nie wiem jaki masz routing. Skoro użyłeś scaffolda zakładam, że defaultowy resources.
Jeżeli tak, powinno Ci to stworzyć ściężkę do akcji #show dla CanididateProfilesController podobną do poniższej:

link_to 'Wybór klas', candidate_profile_path(candidate.id)

Wtedy w samym kontrolerze robisz (z głowy):

@candidate_profiles = Candidate.find(params[:id]).candidate_profiles

Wstawiłem

link_to 'Wybór klas', candidate_profile_path(candidate.id)

i

@candidate_profiles = Candidate.find(params[:id]).candidate_profiles

i teraz mam taki błąd

ActiveRecord::RecordNotFound (Couldn’t find Candidate without an ID):
app/controllers/candidate_profiles_controller.rb:7:in `index’

czyli dalej params[:id] jest NULL.

Aha. Jakby to miała być jakaś wskazówka to używam Rails 4.0.0 i Ruby 2.0.0

Poniżej link do całego kodu i do aplikacji na heroku

https://github.com/pablosz/rekrutacja
Link do aplikacji

A jak wygląda url, pod który wchodzisz?

http://warm-ridge-9656.herokuapp.com/candidate_profiles.1

routes.rb:

resources :candidates do resources :candidate_profiles end
CandidateProfilesController#index:

@candidate_profiles = Candidate.find(params[:candidate_id]).candidate_profiles

view:

link_to 'Wybór klas', candidate_candidate_profiles_path(candidate)

To chyba powinno zadziałać. Choć jest na tyle późno, że nie mogę zaręczyć, że czegoś nie pomieszałem. :slight_smile:

Działa :slight_smile:

Wielkie dzięki Lypa.

Muszę się wziąść za czytanie. Wszystko jest opisane w
http://guides.rubyonrails.org/routing.html

Swoją drogą musiałem w widokach dla CandidateProfile zmodyfikować wszystkie ścieżki. W kontrolerze candidate_profiles musiałem dodać

def create @candidate_profile = CandidateProfile.new(candidate_profile_params) @candidate_profile.candidate_id = params[:candidate_id]
W _form.html.erb dodałem :url

<%= form_for(@candidate_profile, :url => candidate_candidate_profiles_path(@candidate_profile.candidate_id)) do |f| %> <% if @candidate_profile.errors.any? %>
Nie ma jakiegoś prostrzego sposobu na na relację has_many.
Może jest jakiś generator w stylu

rails g scaffold candidate has_many candidate_profiles …

I nie wiem jak zrobić ścieżkę tego typu

candidates/:id_candidate/candidate_profiles/:id dla UPDATE

w index.html.erb

edit_candidate_candidate_profile_path(candidate_profile)

i wychodzi

candidates/:id_candidate_profile/candidate_profiles

Nie wiem gdzie wstawić @candidate_id, bo mam taką zmienną

edit_candidate_candidate_profile_path(@candidate_id, candidate_profile)

Swoją drogą, to candidate_candidate_profile_path wygląda trochę kretyńsko. :slight_smile: Na Twoim miejscu przemianowałbym np. kontroler CandidateProfilesController na Candidate::ProfilesController - tak jak opisują to tutaj.
Wtedy url-helpery wyglądałyby mniej więcej tak:

candidate_profies_path