Wprowadzanie danych do dwóch powiązanych ze sobą tabel (to_one)

Witam serdecznie,

niedawno miałem podobny problem (http://rubyonrails.pl/forum/t2940-automatyczne-dodawanie-do-tabeli-id-u�ytkownika) jednak ten jest trochę inny dlatego zakładam nowy wątek (poprzedni wątek w połowie mi jedynie pomógł).

Otóż mam sobię dwie tabelki w bazie (co za tym idzie dwa modele):

  • users
  • user_details

mam formularz rejestracji konta:

[code]<% form_for @user, :url => account_path do |f| %>
<%= f.error_messages %>

<%= f.label :email %>
<%= f.text_field :email %>

<%= f.label :password, form.object.new_record? ? nil : “Change password” %>
<%= f.password_field :password %>

<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>

<% f.fields_for :user_details do |details_form| %>
<%= details_form.label :name %>
<%= details_form.text_field :name %>

<%= details_form.label :surname %>
<%= details_form.text_field :surname %>

<% end %>
<%= f.submit “Register” %>

<% end %>[/code]
jak widać wyświetlone zostały ze sobą pola z dwóch modeli.
Wszystko fajnie, idziemy dalej.

Mam także metodę create w kontrolerze UsersController:

[code] def create
@user = User.new(params[:user])
@user_details = UserDetails.new(params[:user][:user_details])

if @user.save && @user_details.save
  @user.deliver_user_verification_instructions!
  flash[:notice] = "Registration verification email sent.Please verify your account!"
  redirect_back_or_default account_url
else
  render :action => :new
end

end[/code]
prawie wszystko działa. No właśnie prawie robi dużą różnicę :wink:
Otóż czego nie mogę osiągnąć:

  1. ten zapis:

[code]@user = User.new(params[:user])
@user_details = UserDetails.new(params[:user][:user_details])

if @user.save && @user_details.save[/code]
wydaje mi się błędny, gdyż nie zapisuje id z modelu User do user_id w modelu UserDetails, ponadto nie podoba mi się, że muszę robić
dwie czynności @user.save i @user_details.save - chciałbym zrobić tak by było tylko @user.save, tym samym dwie linijki wyzej przydało by się zoptymalizować pod tym kątem. I na tym poległem - czy istnieje możliwość krótszego, bardziej zoptymalizowanego oraz eleganckiego zapisu?

  1. Drugi mój problem jest taki iż w <%= f.error_messages %> dostaje errory tylko z modelu User moje pytanie brzmi jak może z merge’ować błędy, które dostaje od authlogica (bo z niego korzystam) z błędami dot. nowych pól?

Będę wdzięczny za pomoc.
Pozdrawiam

Nie odpowiem na Twoje pytanie, tylko zadam Ci kolejne: czy jest jakikolwiek powód dla którego UserDetails jest wyodrębniony jako kolejny byt, czyli model + tabelka w bazie danych? Dlaczego nie możesz po prostu tych danych przechowywać w jednej tabeli?

Rozwiązanie takie jakie proponujesz wydaje mi się dziwaczne i szukam jakiegoś wytłumaczenia dlaczego to chcesz robić ale nie potrafię go wymyślić :).

musi być w dwóch tabelach z kilku względów:

  1. tabela / model User jest z pluginu authlogic i jak przyjdzie do jakiejś aktualizacji to nie będę musiał w nią ingerować dodatkowo
  2. dane z tabeli / modelu UserDetails będą wykorzystywane znacznie rzadziej niż w User
  3. kolejnym powodem jest to, że chciałem się po prostu nauczyć operować na relacjach w railsach bo jak widać z mojego pierwszego posta jestem początkującą osobą w RoR a z doświadczenia wiem, że takie sytuacje w późniejszych projektach będą i chciałbym umieć na takich strukturach działać.

tak jak napisałem wcześniej, że zaprezentowane rozwiązanie (choć po części działajace) jest dla mnie na logikę niepoprawne dlatego po całym dniu spędzonym w google, pytam się na forum licząć na rozwiązanie mojego problemu :slight_smile:

http://rubyonrails.pl/forum/p18448-2010-05-02-22%3A39%3A25#p18448
doczytaj dokładnie post, który podlinkowałem - powinien rozwiązać Twoje problemy

No właśnie ten link przeglądałem kilkanaście razy (m.in. za pomocą tego rozwiązałęm problem wyświetlania formularza z dwóch tabel) i nie mogę dostrzec rozwiązania - czy przypadkiem chodzi o coś powiązanego z tym: accepts_nested_attributes_for ?

W twoim przypadku wystarczy:

[code=ruby]class User < ActiveRecord::Base
has_one :user_profile
accepts_nested_attributes_for :user_profile
#reszta kodu
end

class UserProfile < ActiveRecord::Base #Details może nie zadziałać bo dziwnie wygląda to nazewnictwo, wymusza liczbę mnogą dla modelu
belongs_to :user
end

#widok

<% form_for @user, :url => account_path do |f| %>
<%= f.error_messages %>

<%= f.label :email %>
<%= f.text_field :email %>

<%= f.label :password, form.object.new_record? ? nil : “Change password” %>
<%= f.password_field :password %>

<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>

<% f.fields_for :user_profile do |profile_form| %>
<%= profile_form.label :name %>
<%= profile_form.text_field :name %>

<%= profile_form.label :surname %>
<%= profile_form.text_field :surname %>

<% end %>
<%= f.submit “Register” %>

<% end %>

#kontroler

@user = User.new(params[:user])

if @user.save[/code]
Wszystko z palca, ale masz ogólny zarys jak to powinno wyglądać :slight_smile:

ehhh akurat to miałem od początku - z details zmienilem na detail chyba powinno być ok ?
najbardziej mnie interesuje tak jak wyzej napisałem akcja create którą mam źle.

To co Tubis przedstawiłes to akurat to co całkowicie rozumiem.

@user = User.new(params[:user])
zapisuje tylko dane do tablicy Users - i z tym walcze

accepts_nested_attributes_for :user_profile

Ta linijka powinna powodować zapis do obu tabel.

Detail nie bardzo pasuje z kwestii językowej, ale to już wedle uznania :slight_smile:

W takim razie nie mam pojęcia czemu nie działa.

Wiem nie mnie jednak na obecną chwile jest to detal :]

Poniżej przedstawiam pokrótce mój kod:

Model User:

[code]class User < ActiveRecord::Base
has_one :user_detail
accepts_nested_attributes_for :user_detail
attr_accessible :email, :password, :password_confirmation, :user_detail_attributes



end[/code]
Model UserDetail:

class UserDetail < ActiveRecord::Base belongs_to :user end
Kontroler:

[code]class UsersController < ApplicationController
def new
@user = User.new
@user.build_user_detail
end

def create
@user = User.new(params[:user])
if @user.save
redirect_back_or_default account_url
else
render :action => :new
end
end



end[/code]
Widok w pierwszym poście napisałem.
Dodatkowo załączam migracje user_details (users jest taka jak w docu dla authlogic’a):

[code]class CreateUserDetails < ActiveRecord::Migration
def self.up
create_table :user_details, :id => false do |t|
t.integer :user_id
t.string :user_type, :null => false, :default => ‘u’, :limit => 1
t.string :name, :null => false, :limit => 20
t.string :surname, :limit => 30
end

execute "ALTER TABLE user_details ADD CONSTRAINT fk_user_details_users FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE"

add_index :user_details, :user_type
add_index :user_details, :name

end

def self.down
execute “ALTER TABLE user_details DROP FOREIGN KEY fk_user_details_users”
drop_table :user_details
end
end[/code]
będę wdzięczny za pomoc.

Może przyda się jakis początek w debug.owaniu.
Nie wchodząc w tematu ruby_debug najprosciej będzie

def create @user = User.new(params[:user]) puts @user.inspect ... end
Następnie zerknij w logi, dobrze jak już masz podgląd.
Sprawdź czy wszystko jest ok. Powinno być coś w stylu {“user”=> {“email” => ‘’", “user_detail_attributes” => {“surname” => “”}}}

Jesli masz user_details_ zamiast user_detail_ to juz wiesz, że trzeba zmienić w formularzu na :user_detail.

Tablica taka się generuje wraz z zapełnionymi danymi (aby nie było problemu zmienilem z user_detail(s) na profile(s)).
Problem prawdopodobnie leży prze zapisie tzn
if @user.save

to cały czas zapisuje dane do jednej tabeli.
Jakieś pomysły ?

  1. Zobacz czy w logach nie ma żadnych warningów.
  2. Spróbuj zakomentować linijkę z attr_accessible
  3. Zobacz czy obiekty są poprawnie zapisywane kiedy próbujesz je utworzyć z poziomu konsoli (script/console)

Czemu zmieniasz na profiles z user_details?

  1. W bazie masz tabelę profiles czy user_details?
  2. Które z poniższych masz w formularzu?
    a) f.fields_for :user_details do |details_form|
    b) f.fields_for :user_detail do |detail_form|
  3. Podaj wartości wyciągnięte z @user.inspect

Aha i oczywiście nie zapomnij w modelu User o dodaniu ‘accepts_nested_attributes_for :user_detail’ (jesli w bazie masz user_details)

Zmieniłem na profiles aby nie było prościej czytać bo jak wcześniej ktoś powiedział user_details jest formaą poprawną a ror rozpoznaje liczbę mnogą i mogły być na różnym poziomie różne problemy.

  1. Wszędzie teraz jest profile lub profiles (w zależności gdzie to się znajduje).
  2. f.fields_for :profiles do |p|
  3. hmm faktycznie wartości nie są przypisywane do odpowiednich pól :confused:

[code]— &id003 !ruby/object:User
_sessions: []

attributes:
last_login_at:
updated_at: &id001 2010-06-18 06:39:28.768261 Z
last_request_at:
crypted_password: 71e170d3825d1cf13fa60888e8808b0a28be066a03ed5ebd8a5e78e470bf166fda20fb6b3cb74539cac5fad7ed2262ec212253740a5e4ffecce5bc7884180c01
current_login_ip:
perishable_token: DQtr2hQC7U34ooSrHQHZ
single_access_token: eMoyqnJo_9zMT9krQFGD
current_login_at:
password_salt: rLnaQsUdWy5uuM14x7lB
id: 1
failed_login_count: 0
login_count: 0
persistence_token: f5fddcfc05f3c82df65cf2c5d8e5cea6d8c905325976cf8cbbad68e1b175f3e5f796a7638b3964091aa2d779c1d845f21750c305fd5b660c388d9adf81a223a4
verified: false
created_at: *id001
last_login_ip:
email: qwerty@wp.pl
attributes_cache: {}

changed_attributes: {}

errors: !ruby/object:ActiveRecord::Errors
base: *id003
errors: !omap []

new_record: false
password: “12345”
password_changed:
password_confirmation: “12345”
profile: &id002 !ruby/object:Profile
attributes:
name:
surname:
user_type: u
user_id: 1
attributes_cache: {}

changed_attributes:
user_id:
errors: !ruby/object:ActiveRecord::Errors
base: *id002
errors: !omap []

new_record: true
skip_session_maintenance: false[/code]

to mam od samego początku.

ponad to oprócz samego tego:

<% f.fields_for :profile do |p| %>

mam tak:

<% @user.build_profile unless @user.profile %> <% f.fields_for :profile do |p| %>
gdyż bez tego wpisu w ogole w tablicy nie bylo danych z drugiego formularza.

Może ten http://railscasts.com/episodes/196-nested-model-form-part-1 coś pomoże

[quote=mits]musi być w dwóch tabelach z kilku względów:

  1. tabela / model User jest z pluginu authlogic i jak przyjdzie do jakiejś aktualizacji to nie będę musiał w nią ingerować dodatkowo
  2. dane z tabeli / modelu UserDetails będą wykorzystywane znacznie rzadziej niż w User
  3. kolejnym powodem jest to, że chciałem się po prostu nauczyć operować na relacjach w railsach bo jak widać z mojego pierwszego posta jestem początkującą osobą w RoR a z doświadczenia wiem, że takie sytuacje w późniejszych projektach będą i chciałbym umieć na takich strukturach działać.[/quote]
  4. Nie powinno z tym być problemu, dawno nie używałem authlogic ale na pewno dodawałem dodatkowe dane do modelu użytkowników.
  5. Martwisz się że przez to aplikacja będzie działać wolniej? Jest to jakiś śladowy narzut, ale to nawet zależy od tego jak to napiszesz - rozbicie na tabele może spowalniać te akcje gdzie trzeba będzie zrobić join w celu wydobycia obu tabelek, mysql się na tym zarzyna, chociaż pewnie nie na tak prostej asocjacji.
  6. Na prawdę moim zdaniem w tym momencie nie potrzebujesz dodatkowej struktury – trzeba też się nauczyć że za dużo asocjacji wcale nie jest dobre.

@mits

ad 1. Nie musisz się martwić o jakieś konflikty, tak długo jak nie używasz jednej z nazw zastrzeżonych dla “magicznych kolumn”

Tu masz napisane co to za kolumny:
http://github.com/binarylogic/authlogic/blob/master/lib/authlogic/session/magic_columns.rb

dzięki railscasts pomógł :slight_smile: nie wiem co było źle ale zrobiłem od nowa i działa.

[quote]1. Nie powinno z tym być problemu, dawno nie używałem authlogic ale na pewno dodawałem dodatkowe dane do modelu użytkowników.
2. Martwisz się że przez to aplikacja będzie działać wolniej? Jest to jakiś śladowy narzut, ale to nawet zależy od tego jak to napiszesz - rozbicie na tabele może spowalniać te akcje gdzie trzeba będzie zrobić join w celu wydobycia obu tabelek, mysql się na tym zarzyna, chociaż pewnie nie na tak prostej asocjacji.
3. Na prawdę moim zdaniem w tym momencie nie potrzebujesz dodatkowej struktury – trzeba też się nauczyć że za dużo asocjacji wcale nie jest dobre.[/quote]
tak jak wspomniałem chciałem się tylko nauczyć, jeśli będę robił coś podobnego w prawdziwym projekcie to napewno zastosuje się do twojej rady.