views:

51

answers:

1

hey,

When using accepts_nested_attributes_for, I got stuck when having a validation which required the original to be present. The code will help clear up that sentence.

class Foo < ActiveRecord::Base
  has_one :bar
  accepts_nested_attributes :bar
end

class Bar < ActiveRecord::Base
  #property name: string
  belongs_to :foo
  validates_presence_of :foo #trouble line!
end

#now when you do
foo = Foo.create! :bar_attributes => {:name => 'steve'}
#you get an error because the bar validation failed

I would like to write a validation that goes something like...

class Bar < ActiveRecord::Base
  validates_presence_of :foo, :unless => :being_built_by_foo?
end

I am currently using rails3.beta4

Thank you

Alas I don't have an answer to this post, but the I came up with another way so I didn't need the validation.

Since bar should never be without a foo then any request to create a bar without a foo_id is an error. In the real example a foo is a project, and bar is a bid. It is a nested resource, but I wanted to give access to json apps to be able to query the info from the /bids location so the router looked like.

resources :bids
resources :projects do
  resources: bids
end

and then I just had to make sure all html access used project_bids_path or form_for [:project,@bid] etc. This next part is largely untested but so far the desired behavior is there. I got the idea from Yehuda's post on generic actions http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/

#I'm sure there is a better way then map.connect
map.connect "projects/invalid_id", :controller => "projects", :action => "invalid_id"
resources :projects
  resources :bids
end

#couple of changes from Yehuda
def redirect(*args, &block)
  options = args.last.is_a?(Hash) ? args.pop : {}

  path = args.shift || block
  path_proc = path.is_a?(Proc) ? path : proc {|params| path % params }
  status = options[:status] || 301

  lambda do |env|
    req = Rack::Request.new(env)
    #Get both the query paramaters and url paramaters
    params = env["action_dispatch.request.path_parameters"].merge req.params
    url = path_proc.call(params.stringify_keys)
    #Doesn't add the port back in!
    #url = req.scheme + '://' + req.host + params
    #content-type might be a bad idea, need to look into what happens for different requests
    [status, {'Location' => url, 'Content-Type' => env['HTTP_ACCEPT'].split(',').first}, ['Moved Permanently']]
  end
end

def bid_path
  redirect do |params| 
    if params['project_id']
      "/projects/#{params['project_id']}/bids/#{params['id']}" 
    else
      '/projects/invalid_id'
    end
  end
end


match "bids", :to => bid_path
match "bids/:id", :to => bid_path

however, after doing all of this I most definitely don't think it worth it. I think nested_attributes breaks things and can be improved if that validation doesn't work, but after looking through the code for a little while I'm not sure exactly how to fix it or if it's worth it.

A: 

first of all, when using nested_attributes, you'll get the presence of the container. in the example: when you save Foo and there's also a nested form for Bar, then Bar is built by Foo.

I think there's no need to make this kind of validation if you're sure to use Bar only in contexts with Foo.

btw, try to write validation as follow (new preferred syntax for Rails3):

validates :foo, :presence => true

hope this helps, a.

apeacox
Thanks for the formatting tip! The problem I'm having is coming from trying to make a nice interface. There is a JSon API part to the project as well. In reality bar is a bid, and foo is a project. From the web UI when you create a project you can set up bids using the nested_attributes. After the project you can edit bids on the projects page which uses nested_attributes. On the json side I wanted someone to be able to create bids by posting to the bids controller with a project_id. To do this I need validation that the project_id is present, which for now is unfortunately in controller.
daicoden
you can always do a nice workaround without using json-based requests just using a little bit of JS. for example, you make a nested form for bid, then with JS you create a link/button to add a new bid form (you can add as many bids as you want, by just clicking). when you edit/save the project you'll get bids saved correctly. I can give you some example code if you're interested in this solution ;-)
apeacox
That's a good idea! I still think you should be able to set up that validation!!! >.< However, the examples I keep thinking of are more and more contrived. I think I can setup the javascript for another part of the project, but I appreciate the offer. For this bid problem in particular I actually needed to make it a nested resource because no bid can exist without a project. Thanks for your help!
daicoden