views:

328

answers:

2

I have a couple models (Site and Server) that are related to eachother via has_many :through. they also both belong_to :user. in my sites/new view, I create a new site, and I create a new server using a nested form.fields_for :servers. Everything works as expected, except for that the server that ends up getting created doesn't have a user_id populated. How do i ensure it is?

My sites_controller new and create methods:

 def new
    @user = current_user
    @site = @user.sites.build
    @servers = @user.servers.all

    # let there be one server linked
    @site.site_servers.build

    # @user.servers.build if @user.servers.empty?
    @site.servers.build( :user_id => current_user.id ) if @site.servers.empty?

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @site }
    end
  end

  def create
    @site = current_user.sites.build(params[:site])

    respond_to do |format|
      if @site.save
        flash[:notice] = 'Site was successfully created.'
        format.html { redirect_to(@site) }
        format.xml  { render :xml => @site, :status => :created, :location => @site }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @site.errors, :status => :unprocessable_entity }
      end
    end
  end

If you notice the commented lines, those are things I tried that didn't work.

Models:

class User < ActiveRecord::Base
  acts_as_authentic

  has_many :sites
  has_many :servers
end

class Site < ActiveRecord::Base

  belongs_to :user

  has_many :site_servers
  has_many :servers, :through => :site_servers

  accepts_nested_attributes_for :site_servers, :allow_destroy => true
  accepts_nested_attributes_for :servers, :allow_destroy => true

  validates_presence_of :name, :on => :create, :message => "Name is required"
end

class Server < ActiveRecord::Base
  attr_encrypted :password, :key => '393b79433f616f445f652a752d', :attribute => 'crypted_password'

  belongs_to :user

  has_many :site_servers
  has_many :sites, :through => :site_servers

  validates_presence_of :url, :on => :create, :message => "URL is required."
  validates_presence_of :username, :on => :create, :message => "Username is required."
  validates_presence_of :password, :on => :create, :message => "Password is required."


  def name
    username + "@" + url
  end

  def to_s
    name
  end

end

class SiteServer < ActiveRecord::Base
  belongs_to :site
  belongs_to :server

  has_one :user, :through => :site
end

And here's my schema:

ActiveRecord::Schema.define(:version => 20091203045550) do

  create_table "servers", :force => true do |t|
    t.string   "url"
    t.string   "username"
    t.string   "crypted_password"
    t.integer  "port"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "site_servers", :force => true do |t|
    t.integer  "site_id"
    t.integer  "server_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "sites", :force => true do |t|
    t.string   "name"
    t.string   "url"
    t.string   "path"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "users", :force => true do |t|
    t.string   "username"
    t.string   "email"
    t.string   "crypted_password"
    t.string   "password_salt"
    t.string   "persistence_token"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end
+1  A: 

I'm guessing the problem is in your create action. The new action just builds Ruby objects -- you need to make sure there is similar build code in the create action:

def create
  @site = current_user.sites.build(params[:site])
  @site.save
end
bensie
Thanks for the reply. I just pasted my create method above as well. I was already using the exact code you posted. I think the reason is because the server is being created as a child of site. Site just thinks it needs to maintain the relationship between site and server, which it does. How would site know that it also needs to tell server the right user_id too?
Trevor Hartman
Does it need to be done manually? Seems kinda hackish, but maybe I could do:@site.servers.first.user_id = current_user.idin my create method.
Trevor Hartman
I see what you're saying now, but am having a very hard time trying to figure out what you're trying to accomplish. Why are you using a has_many :servers, :through => :site_servers? I'm not understanding the join table's purpose. The way I see it, User should has_many :sites, Site should has_many :servers, and User should has_many :servers, through => :sites.
bensie
Because Servers should be able to be independently managed by a user. A user should be able to setup a bunch of Servers and wire them to Sites at will. Several sites could have the same servers in their collection.
Trevor Hartman
In that case, yes you'd need to add that attribute manually. I'd add an accessor for the current_user attribute and set it in a callback method in the model.
bensie
thanks for your help. i'll set it manually.
Trevor Hartman
+1  A: 

Do you have a hidden field for the user_id in the server form?

<%= f.hidden_field :user_id %>

If not, the value is not getting passed back, even if you managed to properly set it. The line you have commented out would have worked, if you add a hidden field to the form.

@site.servers.build(:user_id => current_user.id) if @site.servers.empty?

I actually like the idea of setting the user id in the create method better, because otherwise you introduce the possibility of someone crafting up their own form submission and creating things under other people's user ids. I don't know if security is a big deal in your app, but I never trust a user id that is sent from a form.

MattMcKnight
ahh. didn't realize I need to do that to persist the id. makes sense. i'll populate manually since you brought up the security issue. thanks.
Trevor Hartman
Uhh yeah huge security issue there -- never do that.
bensie