Nadpisanie metod modelu

Mam następujący model:

[code=ruby]class Member < ActiveRecord::Base
belongs_to :group
has_many :member_posts
has_many :posts, :through => :member_posts

validates_existence_of :group
validates_presence_of :firstname, :lastname
validates_length_of :firstname, :lastname, :within => 2…32
end[/code]
Chcę nadpisać metody create, update i destroy żeby działały w następujący sposób:

def create(params) self.firstname = params[:firstname] self.lastname = parans[:lastname] unless params[:group].nil? group = Group.find_by_id params[:group].to_i self.group << group unless group.nil? end unless params[:posts].each do |post| post = Post.find_by_id post.to_i self.posts << post unless post.nil? end return false unless self.valid? self.save true end
Metody update i destroy wyglądałyby podobnie, w internecie znalazlem kilka rozbieżnych względem siebie rozwiązań. Pierwsza (ale dla inicjalizatora) trzeba wykonać polecenie super. W innych rozwiąznich była mowa że trzeba użyć callback lub inne rozwiązania, które mi nie zadziałały, pewnie coś pominołem.

Problem mam z tym, że jak wywołuje akcje create w kontrolerze to przypisuje pewne wartości do zmiennych które de facto nie istnieją w modelu, wprawdzie mogę zdefiniować własne, dodatkowe atrybuty w modelu, ale jak nie wywoła sie moja metoda, to nie zostaną wykonane odpowiednie akcje z ich uwzględnieniem.

Ogólnie chodzi mi o to, że przy posiadaniu odpowiednich wartości w params i odpowienim ich obrobieniu chcę wywołać jednolinijkowe polecenie dla modelu, a całą logikę biznesową mieć wewnątrz modelu.

takie rzeczy robisz w kontrolerze + w callbackach - before/after _ create/save - polecam poczytać na ten temat :wink:

niektorzy twierdzą, których niedawno poznałem osobiście (i w Railsach siedzą długo), że jeśli kontroler jest duży a jakby to zobaczyli to powiedzieliby że jest duży to jest źle napisany, bo wszystkie odniesienia do modelu powinny być w modelu a kontroler jest tylko do przekazania parametrów.

taka jest praktyka, że kontroler powinien posiadać możliwie najmniej kodu, cała zabawa powinna odbywać się w modelu.
szczerze mówiąc, dopiero teraz rzuciłem okiem na kod przedstawiony przez Ciebie - tak naprawdę to nie potrzebujesz tutaj nic nadzwyczajnego w kontrolerze, ani callbacków - poczytaj coś więcej na temat relacji w railsach + powinno się przydać nested_attributes - ale niechc ktoś mnie poprawi, jestem niedysponowany w momencie pisania postu

A nie lepiej zrobić metodę np ‘create_with_group_and_posts’, która wykona co trzeba?

I jak mnie wzrok nie myli zamiast

return false unless self.valid? self.save true
wystarczy

save

zadziała tak samo , self używaj przypisując wartości do atrybutów modelu.

Po pierwsze, jak robisz takie rzeczy:

group = Group.find_by_id params[:group].to_i

to możesz trochę zaoszczędzić:

group = Group.find params[:group]

Find z założenia szuka po id i zamieni Ci stringa na integera.

Po drugie, jak już robisz

unless params[:group].nil? group = Group.find_by_id params[:group].to_i self.group << group unless group.nil? end
to możesz sobie darować i zrobić

self.group_id = params[:group]

Jeśli będzie nil, to się przypisze nil i będze ten sam efekt.

Ale to są pół środki, bo ogólnie masz to trochę od zakrystii strony zrobione :wink: Railsy dbają o to, żebyś nie musiał pisać takiego kodu w modelu, a kontroler dalej pozostawił czysty.

W kontrolerze tak na oko powinno to wyglądać tak:

@member = Member.create(params[:member])

I już ;] Masz skinny controller i przy okazji skinny model. I teraz, żeby to zadziałało, to musisz dobrze zrobić formę.

Parametry powinny wyglądać mniej więcej tak:

{ :member => { :firstname => 'Juzek', :lastname => 'Kowalski', :group_id => 8, :post_ids => [2,3,4] }}

Jeżeli składasz formy samemu i masz te params tak jak napisałeś, to nie jest to najlepszy sposób. Po to są helpery do form, żeby ich używać :>

I rada na przyszłość, przeczytaj rails guides, bo widać, że średnio zaglądałeś, a nawet ludzie zaawansowani do nich wracają często.

Na przykład w kontekście Twojego pytania:
http://guides.rubyonrails.org/form_helpers.html

Pozdrawiam

EDIT:
@krzyzak
Nested attributes są potrzebne dopiero, gdyby istniała potrzeba tworzenia nowych grup, gdy tworzy się rekord member. Tutaj używa się tylko asocjacji.

Dziękuję za waszą pomoc. Czytam sukcesywanie rails guides, ale jest tego względnie dużo i widocznie helpery jakoś pominąłem.

Fajnie :slight_smile:

Przejrzyj całość pobieżnie, a dokładniej te elementy, które Ci są potrzebne.

Odświeżam “stare śmieci” (mam nadzieje, że nikogo nie uraziłem tym lekki porównaniem z przekąsem). Optymalizuje właśnie aplikacje pod kątem zapisywania rekordów w bazie za pomocą jednego modelu.

Natrafiłem na pewien problem w widoku.

= select :course, :unit_ids, options_for_select(@list.collect {|p| [ p[:value], p[:name] ] }, @rel), {}, {multiple: true, size: 5}

tworzy selecta z parametrem name (zły efekt końcowy)

course[unit_ids][]
= f.select :unit_ids[], options_for_select(@list.collect {|p| [ p[:value], p[:name] ] }, @rel), {}, {multiple: true, size: 5}

wyświetla błąd o braku atrybutu unit_ids, wiem źe można to zamaskować poprzez

attr_accessor :unit_ids

ale nie o to chodzi.

Chcę poprostu wysłać listę :course => {:unit_ids=> [1,2,3]}
przy użyciu konstrukcji f.select (czy jakoś się da bez ustawiania attr_accessor) ?

zamiast f.select możesz spróbować użyć http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag stworzy to selecta, który nie jest powiązany z modelem dla którego renderujesz formularz, więc nie będzie przesłany w params[nazwa_modelu]. Ewentualnie możesz próbować tez zmienić parametr name dla selecta, ale nie próbowałem.

zrobilem przy uzyciu select, ale nie konstrukcja f.select tylko samo select. Teraz niestety jak wysyłam obiekt i po przejściu walidacji otrzymuje bląd invalid

class Course < ActiveRecord::Base has_many :relation has_many :units, :through => :relation ...
To otrzymuje po wysłaniu formularza

"course"=>{... "unit_ids"=>[1] ...}

i obiekt po walidacji dostaje błąd

{:units=>["invalid"]}