1

私は最初のRailsプロジェクトに取り組んでおり、適切なユーザー/アドレス(またはユーザー/場所)の関連付けを作成しようとしています. 私の問題はよくあることで、他の人が私が試みていることをうまくやっていて、答えを見つけるのは簡単だろうと思っていましたが、そうではありませんでした. 非常に異なる答えを持つ多くの異なる質問があり、私の場合はどうすればよいかわかりませんでした。これが私のシナリオです:

場所は、国、州/地域、または市区町村にすることができますが、それ以上具体的ではありません (私は Google Places Library のオートコンプリート機能を使用しています)。usersそのため、 1 人以上のユーザーが同じ都市 (国、州、都市、緯度、経度など) に住むたびに、テーブル内の多くの情報が重複するのを避けるために、テーブルが必要であると考えましたlocations。さらに悪いことに、ユーザーには「home_location」と「current_location」が必要です。関連付けは次のとおりです。

has_one

最初は、「まあ、ユーザーには住所/場所があるので、おそらくhas_one関連付けを使用する必要があります。しかし、を使用するuser has_one locationと、場所テーブルはユーザーのテーブルを指すものになることがわかりました。これは何ですか?多くのユーザーが同じ場所を参照できるはずなので、欲しいです。

属している/has_many

「よし、じゃあbelongs_to/has_manyアソシエーションを使おう」と思いました。最初はかなりきれいに見えました。とても理にかなっているように見えました。ネストされたフォームの宝石を使用しています。これは、これまでに行った他のすべてのものでうまく機能し、場所のネストされたフォームが読み込まれ、情報が正しく送信されました。場所が作成され、データベースに正常に追加されましたが、1 つの問題がありました: user.location が保存されませんでした。その理由を突き止めようとしていると、この全体に非常に奇妙な何かがあることに気付きました。Rails は、この設計構造を私が必要とする方法で扱っていません (私が感じる方法はより直感的です)。実際、通常、この関連付けは「ユーザー/マイクロポスト」に使用されます ( Hartl のチュートリアルのように))と、こういうこと。しかし、私の場合は考え方が異なります。私たち人間はそれを理解していますが、Rails はそうではないようです。ユーザーが自分の「プロフィールの編集」ページで自分の住んでいる場所を言うことができるようにしたい. しかし、私が読んだことから、レールはLOCATIONの編集ページにユーザー用のネストされたフォームがあることを期待しているようです。次のようなユーザーを作成することを期待しているように:

@location.user.create(:email=>"john@example.com")

私が必要としているのは、次のような場所を作成することです:

@user.location.create(:city=>"Rio de Janeiro", etc)

わかった。私が求めているのは具体的すぎて、Rail の標準的な関連付けがうまくいかないのかもしれません。上記のすべてのコードは重複した都市を作成するため、次のようなものが必要になります。

@user.location.find_or_create(:city=>"Rio de Janeiro")(これは関連付けられたオブジェクトにも存在しますか?)

場所を正常に保存したが、関連付けを作成しなかったこの試行のコードを次に示します。

モデル

class User < ActiveRecord::Base
  attr_accessible ... :home_location, :home_location_attributes,
                  :current_location, :current_location_attributes, etc


  belongs_to :home_location, :class_name => 'Location'
  accepts_nested_attributes_for :home_location

  belongs_to :current_location, :class_name => 'Location'
  accepts_nested_attributes_for :current_location

  ...
end

class Location < ActiveRecord::Base
  attr_accessible :google_id, :latitude, :longitude,
                  :administrative_area_level_1, :administrative_area_level_2,
                  :city, :neighborhood, :country, :country_id

  belongs_to :country

  has_many :users
end

スキーマ

  create_table "users", :force => true do |t|
    ...
    t.integer  "home_location"
    t.integer  "current_location"
    t.text     "about_me"
    t.datetime "created_at",                         :null => false
    t.datetime "updated_at",                         :null => false
    ...
  end

  create_table "locations", :force => true do |t|
    t.string   "google_id",                   :null => false
    t.string   "latitude",                    :null => false
    t.string   "longitude",                   :null => false
    t.integer  "country_id",                  :null => false
    t.string   "administrative_area_level_1"
    t.string   "administrative_area_level_2"
    t.string   "city"
    t.string   "neighborhood"
    t.datetime "created_at",                  :null => false
    t.datetime "updated_at",                  :null => false
  end

しかし、私が欲しかったのはあまりにも特殊すぎるようだったので、すべてをあきらめて、単純に追加しようとしましたform_for @location. 次に、レコードが既に存在するかどうかをユーザー コントローラーで確認し、それをユーザーの home_location 属性に保存します。そこで、ユーザー モデルから次の行を削除しました。

accepts_nested_attributes_for :home_location
accepts_nested_attributes_for :current_location

f.fields_foredit_profile ビューの を に置き換えましたform_for @location

次に、コントローラーに場所を処理するコードを追加しました。

class UsersController < ApplicationController
  before_filter :signed_in_user,  only: [:index, :edit, :update, :destroy]
  before_filter :signed_out_user, only: [:new, :create]
  before_filter :correct_user,    only: [:edit, :update]

  ...
  def edit
    if @user.speaks.length == 0
      @user.speaks.new
    end
    if @user.wants_to_learn.length == 0
      @user.wants_to_learn.new
    end
    if @user.home_location.blank?
      @user.home_location = Location.new
    end
  end

  def update
    logger.debug params

    ...

    @home_location = nil

    params[:home_location][:country_id] = params[:home_location].delete(:country)

    unless params[:home_location][:country_id].blank?
      params[:home_location][:country_id] = Country.find_by_iso_3166_code(params[:home_location][:country_id]).id

      #@home_location = Location.where(params[:home_location])
      #The line above describe the behavior I actually want,
      #but for testing purposes I simplified as below:
      @home_location = Location.find(2) #There is a Location with id=2 in the DB

      if @home_location.nil?
        @home_location = Location.new(params[:home_location])
        if !@home_location.save then @home_location = nil end
      end
    end

    if @user.update_attributes(params[:user])
      @user.home_location = @home_location
      @user.save
      flash[:success] = "Profile updated"
      sign_in @user
      #render :action => :edit
      redirect_to :action => :edit
    else
      render :action => :edit
    end
  end
  ...
end

しかし、それでも関連付けは保存されません。実際、コンソールでも、何をしても、ユーザーの home_location 属性がデータベースに保存されることはありません。

何が起こっているのか誰にも分かりませんか??

関連付けが保存されないのはなぜですか? Railsでこれを行う最も正しい方法は何ですか?

質問が長くなってしまい申し訳ありません。誰かが読んでくれますように。とても感謝しています!

ありがとう

編集1

RobHeaton のヒントに従って、User モデルにカスタム セッターを追加しました。

  def home_location= location #params[:user][:home_location]
   locations = Location.where(location)
   @home_location = locations.first.id unless locations.empty?
  end

発生したいくつかの問題を修正した後、機能し始めました!データは最終的にデータベースに保存されました。ただし、コントローラーで機能しなかった理由はまだわかりません。体に何か推測がありますか?間違いが実際には別の場所にあったのではなく、コードがモデル内でよりよく整理されているという理由だけで気づかずに修正してしまったのではないかと思っています。

それでも、問題は完全には解決されていません。値はデータベースに保存されますが、標準的な Rails の方法では関連付けられたモデルにアクセスできません。

1.9.3p362 :001 > ariel = User.first
  User Load (0.5ms)  SELECT "users".* FROM "users" LIMIT 1
 => #<User id: 1, first_name: "Ariel", last_name: "Pontes", username: nil, email: "pontes@ariel.com", password_digest: "$2a$10$Ue8dh/nRh9kL9lUd8JjQU.okD6TtE
i4H1tphmRoNIQ15...", remember_token: "kadybXrh1g0X-BE_HxhA4w", date_of_birth: nil, gender: nil, interested_in: 0, relationship_status: nil, home_location: 3, curr
ent_location: nil, about_me: "", created_at: "2013-05-07 14:28:05", updated_at: "2013-05-09 20:10:41", home_details: nil, current_details: nil> 
1.9.3p362 :002 > ariel.home_location
 => nil

奇妙なことに、Rails は関連付けられたオブジェクトを返すことができないだけでなく、オブジェクトに格納されている整数 ID を返すことさえできません! それはどのように意味がありますか?私は Location オブジェクトを期待していましたが、最悪の場合、3 が返されることを期待していました。しかし、代わりに私はゼロになります。

とにかくカスタムゲッターを書き込もうとしました:

  def home_location
    if @home_location.blank? then return nil end
    Location.find(@home_location)
  end

しかし、もちろんうまくいきません。もっとアイデアはありますか??

4

2 に答える 2

1

解決しました!

グッドプラクティスについて教えてくれた RobHeaton と、Rails の規約を説明してくれた rossta に感謝します。

これが私が今持っている構成です:

モデル

attr_accessible :first_name, :last_name, ...,
                :home_location, :home_location_id, :home_location_attributes,
                :current_location, :current_location_id, :current_location_attributes, ...

...

belongs_to :home_location, :class_name => 'Location'
accepts_nested_attributes_for :home_location

belongs_to :current_location, :class_name => 'Location'
accepts_nested_attributes_for :current_location

スキーマ

create_table "users", :force => true do |t|
  t.string   "first_name"
  t.string   "last_name"
  ...
  t.integer  "home_location_id"
  t.integer  "current_location_id"
  ...
end

私のコントローラーはそのままです。実際には以前よりもきれいになり、パラメーターをいじることはもうありません。

カスタムセッターに関しては、結局のところ、私が質問で説明した目的には必要ありませんでした. しかし、コードを整理するのに役立ち、間違いを見つけやすくなりました。私は今、途中で修正しなければならなかった他のことと、コントローラーをきれいに保つためにそれらを使用しています。

Rails は、begins_to/has_many アソシエーションの両方の「タイプ」を理解しているようです。しかし、どこにも明示的に区別がなされていないのは面白いです。実際には、慣例を尊重するためにいくつかの名前を修正するだけでよいのに、Rails ではそれができないと思い込んでしまいました。

于 2013-05-10T22:21:21.567 に答える