1

我正在开发我的第一个 Rails 项目,我正在尝试创建一个正确的用户/地址(或者更确切地说是用户/位置)关联。我认为我的问题很常见,其他人已经成功地完成了我正在尝试的事情,并且很容易找到答案,但事实并非如此。有许多不同的问题有非常不同的答案,我不知道如何在我的情况下做事。这是我的场景:

该位置可以是一个国家、州/地区或城市,但不会比这更具体(我正在使用 Google Places Library 的自动完成功能)。users因此,为了避免每次一个或多个用户居住在同一个城市(国家、州、城市、纬度、经度等)时表中出现大量重复信息,我想我应该有一张locations表。更糟糕的是,用户应该有一个“home_location”和“current_location”。现在的协会:

has_one

起初我想:“好吧,一个用户有一个地址/位置,所以我应该使用has_one关联。但事实证明,如果我使用user has_one location,位置表将是指向用户表的那个,这不是什么我想要,因为许多用户应该能够引用相同的位置。

属于_to/has_many

“好吧,那我就使用belongs_to/has_many 关联”我想。起初看起来很整洁。似乎说得很有道理。我正在使用嵌套表单 gem,它对我迄今为止所做的所有其他事情都非常有效,并且用于位置的嵌套表单已加载并且信息已正确发送。该位置已成功创建并添加到数据库中,但存在一个问题:未保存 user.location。试图找出原因,我意识到这整件事有些奇怪。Rails 并没有按照我需要的方式处理这种设计结构(我觉得这种方式更直观)。事实上,这种关联通常用于“用户/微博”(就像在Hartl 的教程中一样)) 和这种事情。但在我的情况下,这个想法是不同的。我们人类明白这一点,但 Rails 似乎不明白。我希望用户能够在他的“编辑个人资料”页面中说出他住在哪里。但从我一直在阅读的内容来看,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_for并将edit_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 的提示,我在我的用户模型中添加了一个自定义设置器:

  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

就像是:

class User < ActiveRecord::Base

  def location= city_name
    write_attribute :location, Location.find_by_city_name(city_name)
  end

end

应该这样做。您可以定义state=,city=等等,并且在每种情况下只需按相应的列进行查找。作为一般原则,您希望尽可能多地从控制器中获取此逻辑。一旦您编写了 10 多行代码来处理一些传入参数,您就想考虑是否可以在模型中执行此操作。

于 2013-05-08T17:04:48.920 回答
1

解决了!

感谢 RobHeaton 启发我了解良好实践,感谢 rossta 解释 Rails 的约定。

这是我现在的配置:

模型

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 似乎确实理解 belongs_to/has_many 关联的两种“类型”!有趣的是,尽管在任何地方都没有发现明确的区别。最终假设 Rails 无法做到这一点,而事实上我所要做的就是更正一些名称以尊重约定。

于 2013-05-10T22:21:21.567 回答