views:

686

answers:

2

I have a form that allows me to add files of different formats to a stream. So, a stream is made up of many files, these files are XML files but basically have different schemas. I have one form that allows the user to add whatever file they want, I am using STI (which works great when data is already in the table), my trouble is adding the data to the table.

The form has 1 input field, just a file_field that allows the user to select the file they want to upload. Since I only have one form I'm not able to instantiate the correct object, I have to do it programatically.. and I'm not sure how to do it.

Do I just (or can I) add a drop down with the possible types, and call that field 'type' so that when the form is submitted rails will instantiate the write type of object because the type attribute is provided?

What is the best practice for this.. I'm running rails 2.3.4.

A: 

I don't know how many types you have but I have simply used separate controllers and views for the different types in the past. This way you don't create new object of the base class and try to set the type, you just use the model that inherits from the base class. Each new/edit page for your resources can render a shared partial in the form_for block. The partial would contain your file_field.

This way when the form is submitted it will be going to the correct controller, calling the correct resource.new and everything is ok.

The drawback of course is more files and whatever page you are linking to "add new file" on you need to add multiple links like "add new this type of file", "add new that type of file" etc.

As for setting the type in the form I'm not sure whether that works, I doubt it but just give it a try (Let us know). You could make that type drop down a select_tag and when changed use Javascript to change the action location on the form.

Edited and added basic work around

Not that I like this solution & I doubt its by no means the best, but if you really don't want separate controllers and you need to get it working you could do something like this:

class XmlFile < ActiveRecord::Base
end

class XmlFileTypeA < XmlFile 
end

class XmlFileTypeB < XmlFile 
end

def create
    # Leaving this case statement in the controller for simplicity but you probably want to move this to the model
    case params[:chosen_xml_type]
      when "file_type_a"
        @item = XmlFileTypeA.new(params)
      when "file_type_b"
        @item = XmlFileTypeB.new(params)
      else
        raise "Unknown file type!"
      etc
    end
end
tsdbrown
Yea I really wanted to avoid creating a separate controller for each, even though currently I only have two document types, it could get to be more - quickly.Rails makes it seem so simple to implement the STI.. and it is - but only to retrieve information I cant even find an example of a 'new' form for an STI implementation.
Rabbott
See my edited post, the new action that renders the view would create @xml_file = XmlFile.new, the create action would create the right file type for you based on the users choice. Hope that's clear as mud!?
tsdbrown
This is getting much closer, thanks for the update!Since the controller is what actually instantiates the object wouldn't it have to stay there?I think, after all this research, what I'm looking for is the method in which you can call classify.constanize.new or something like that.. any idea what I'm talking about. You can use the value from the drop down, and create an object of that type..
Rabbott
Yeah I know what you mean. A little meta programming would be a nicer way to do it. In your create action you could do something like @xml_file = Kernel.const_get(params[:chosen_xml_type)).new, make sure chosen_xml_type matches the class name, i.e. "XmlFileTypeA". Depending on your application, i.e if its open to users I would probably run a before filter to just check the param hasn't been tampered with. I.e its in a list that you are expecting.
tsdbrown
Yea I mean I think until I am able to figure something else out this will be the route I take, thanks for your help!
Rabbott
A: 

I found a solution at http://coderrr.wordpress.com/2008/04/22/building-the-right-class-with-sti-in-rails/#comment-1826

class GenericClass < ActiveRecord::Base
  class << self
    def new_with_cast(*a, &b)
      if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
        raise "wtF hax!!"  unless klass < self  # klass should be a descendant of us
        return klass.new(*a, &b)
      end

      new_without_cast(*a, &b)
    end
    alias_method_chain :new, :cast
  end
end

Which worked great for me with minimal code - I don't know if its hackish or not but it works, and is rather clean. I liked the fact that its only 10ish lines of code.

Rabbott