views:

158

answers:

3

I have a partial now looking like this:

<%= render(:partial => 'order', :object => Order.new %>

How can I build a few empty LineItem object into the Order.new as in :object => Order.new?

Note that Order has_many :line_items. and LineItem belongs_to :order

And as a commenter mentioned, this might at first seem to violate the MVC design, but I forgot to mention that this render is really in a link_to_function helper which serves to dynamically insert more fields of the attribute line item.

The actual helper looks like this:

#orders_helper.rb
  def add_line_item_link(name, form_scope)
    link_to_function name, :class => "add_line_item_link" do |page|
      line_item_html = render(:partial => 'line_item', :object => @order.line_items.new, :locals => {:f => form_scope})
      page << %{
        var time_index = new Date().getTime();
        var line_item_html = #{line_item_html.to_json};
        line_item_html = line_item_html.replace(/_\\d+/g, "_"+time_index);
        line_item_html = line_item_html.replace(/\\[\\d+\\]/g, "\\["+time_index+"\\]");
        $('line_items').insert({bottom: line_item_html});
      }
    end
  end

@order.line_items.new is what I like to work on:

first: I want instead of just one line_item to be built in the @order object, I want three. second: the line item has an attribute named 'title', and whenever we get an order, pretty much every time the order has exactly three line items, one has title editor, one has title photographer, and one has title video-editor.

So I thought, maybe I can so something like:

#orders_controller.rb
@titles = %w(editor photographer video-editor)

#orders_helper.rb
...#same as above
:partial => 'line_items', :collection => lambda { @titles.each {|t| @order.line_items.build(:title => t) } return @order.line_items}
...

any suggestions? Thank You

A: 

In response to your changed question--

#order.rb
def default_line_items
  self.line_items.build(:title => "editor")
  self.line_items.build(:title => "photographer")
  self.line_items.build(:title => "video_editor")
  return self.line_items
end
#call to partial
render (:partial => "line_item", :collection => order.default_line_items)
MattMcKnight
Thank You Matt, that worked! That's very clean compared to how I thought I might even approach the problem.
Nik
This method could be a one liner, Not DRY. Also return keyword and explicit receivers are unnecessary.
Steve Graham
No need to make a one liner and not use an explicit return keyword when you are trying to explain something and make it clear.
MattMcKnight
A one liner is perfectly clear. People who don't know any better will see this accepted answer and believe that it's the right way to do things. What will you do if you want to set up more default items? Have a 10 line method?! Also it is **basic** Ruby that the last line of a method is evaluated and returned by the interpreter. Also everything has a receiver, if there isn't an explicit one, the receiver is self. There are times when using self is necessary, this is not one of them.
Steve Graham
Well, I'd probably want to pull this list from the database so it's not hard coded anyway, but seriously...not knowing how much Ruby a questioner asks, it's best to use the most explicit explanation so there's less confusion, at least in my experience training people in Rails.
MattMcKnight
+1  A: 

I'm sorry but this is a serious code smell to me. Violation of MVC principles. The View layer should have no direct interaction with the Model layer whatsoever.

Steve Graham
That's not true at all and is a serious misunderstanding of MVC. If you call @model.property, you are interacting with the model.
MattMcKnight
_Reductio ad absurdum_. I think you're being disingenuous here. I'm referring to Object.new in the View layer. The controller governs interaction between the View and Model layers. Calling a getter method on an instance constructed by the Controller layer is very different from instantiation from within the View layer. At no point should the View layer change the state of the Model layer or call class methods. This is definitely not a misunderstanding, even the the person who asked the question has conceded my point and further clarified his question. I understand MVC very well thank you! :)
Steve Graham
I do try to minimize the instances where I call methods on the model class object inside views so to follow the MVC design. -- In this case of mine, because this is sort of like one of those 'to-do list' apps where you can dynamically add more 'tasks', so I need a place-holding template with all the fields and buttons and etc in place to be inserted into the HTML changing only the id and name parts of the inputs fields with a time stamp so to differentiate them while saving. Because of all this, in the helper method, I do need some sort of empty object to provide correct name and id in inputs.
Nik
Steve, First off, you said "the view layer should no direct interaction with the Model layer whatsoever." That's clearly wrong. Second, the point of MVC (see Fowler, etc.), is dependencies. The model should not be dependent on the view. The view is dependent on the model. Calling Order.new to get a new order is not putting business logic into the view or causing the model to be dependent on the view. MVC compliant.
MattMcKnight
Nik
@nik - @project in the new action no longer exists when in the create action. They are instance variables and they are two separate instantiations of the same class.
Steve Graham
@matt - Granted I should have been clearer initially, but I have since clarified my point and I have no interest in further debating semantics with you. I still maintain that instantiating a model object in the View layer is BAD. What if someone else decided to change the class name? It's simply not maintainable. Besides, I feel that it clouds separation of concerns, the Controller decides how to interact with Model.
Steve Graham
A: 

Refactor of Matt's answer:

def default_line_items
  line_items.build %w(editor photographer video_editor).collect { |i| {:title => i } }
end

One line. Does the same thing.

Steve Graham
I must agree that Matt's illustrative line-by-line approach here in the answer section isn't at all bad; but your shortened version is also very curious and attractive to use. But I can't see how this generates three line-items and if this is valid code? I mean, if it runs, it probably is valid, that's easy enough to check. I asked because I thought each model.build creates only one object in the memory. I can see that the loop is there, but it is after the model.build, will the model.build get 'sucked' into the loop, too
Nik
Base#create and the build method of a collection can take an array of objects to build or create. `%w(editor photographer video_editor).collect { |i| {:title => i } }` will return `[{:title=>"editor"}, {:title=>"photographer"}, {:title=>"video_editor"}]` thus making the code `line_items.build [{:title=>"editor"}, {:title=>"photographer"}, {:title=>"video_editor"}]` this will add three line_item objects to the collection. They will be persisted to the db IF you call save on them or the parent model. The method definitely works, I have used it myself! :)
Steve Graham