Thanks to @unixmonkey and @jqr, I can now add, view and destroy items, linking to the items index page from a menu partial that is called from the character's show page. Success! However, I thought it might be handy to be able to edit items. And that's where my problem comes in. For a while, I was getting a routing error. Then I edited the code and now I'm getting this old chestnut again:
undefined method `item' for #<Character:0x103f0ce50>
What gives? I thought I'd gotten rid of this guy when I defined the character in the items controller.
Here's my edit method from the items controller:
def edit
@character = Character.find(params[:character_id])
@item = @character.item
end
def update
@character = Character.find(params[:character_id])
@item = @character.statistic
if @item.update_attributes(params[:item])
redirect_to character_items_path, :notice => 'Item was successfully updated.'
else
render :action => "edit"
end
end
And here's how I'm calling it from the partial I created for creating and editing items:
<%= link_to 'Edit', edit_character_item_path(@character, item) %>
Any more tips? I'm pretty sure I'm just not writing this correctly. This code is based on what I've got for my other models (statistics, armor class, etc) but I wonder if it might be different based on a couple of things. For one thing, those other models have a has_one relationship with Character, while Items has a has_many. For another, the has_one models are all displayed in the Character show page, while Items is being created from the Items index page. Am I on the right track here? Any advice as to how to solve this issue?
On a less begging for help note, I've spent a good 5 hours working on Manticore this weekend. I notice when I'm working on a personal project and not a tutorial, I can focus for longer periods of time and I find the work more interesting. When doing a tutorial, I feel like it's a valuable use of my time but it's not an interesting use of my time. Does that make sense? Regardless, I absolutely lose track of time when working on my own projects, even if it's just repeatedly butting my head up against trying to get a goddamn edit method to work.
Looking through the project on GitHub, I can see that you have this in character.rb:
ReplyDeletehas_many :items, :dependent => :destroy
Which means you're defining the plural association, not singular. You need to tell Rails which one of the items you want to edit, probably like so:
@character.items.find(X)
Or change it to a has_one association.
Also, this has nothing to do with routing :)
ReplyDeleteWhoops, I don't know why I titled it that way. I think my previous problem was spilling over into this one.
ReplyDeleteA character can have any number of different items, which is why I made it a has_many association. I'm modeling this after the basic blog tutorial, substituting character for post and item for comments. Then once I get this hammered out, I can use that same method to create other models that belong_to character.
So I'm still having trouble understanding what you mean. When you say:
@character.items.find(X)
What should X be? Here's what I've got now:
link_to 'Edit', edit_character_item_path(@character.items.find(item))
but then I get Rails complaining that no route matches.
Switching that code to this:
link_to 'Edit', edit_character_item_path(@character.items.find(@item))
Gives me another, more interesting error:
Couldn't find item without an ID
So I'm guessing this is closer to what I need, but I'm not declaring the id? Switching @item for :character_id, :item_id and :id don't work either.
@_@
Because this is a nested resource you need to pass in both the character and item in your url helpers, I see you are only passing in one item and it is expecting two. Try this:
ReplyDeletelink_to 'Edit', edit_character_item_path(@character, @item)
The "x" is the param you are finding the item with. Since this is the items_controller, params[:id] should be the item id sent when requesting to edit. It is also a good idea to scope it to find off of the character, so someone can't go messing with the item of a different character:
def edit
@character = Character.find(params[:character_id])
@item = @character.items.find(params[:id])
end
@David I'm still getting an error with this updated code. I'm not sure if I've got something wrong in the items controller or what, but now I get this:
ReplyDeleteNo route matches {:controller=>"items", :action=>"edit", :id=>nil, :character_id=>#}
And it only happens when I include a link_to the edit action. Any ideas? Here's a link to the items controller code:
https://github.com/illbzo1/Manticore/blob/master/app/controllers/items_controller.rb
Line 13 of app/views/items/_items.html.erb should have:
ReplyDeletelink_to 'Edit', edit_character_item_path(@character, item)
(no @symbol on item because you are looping over character items and item is the loop variable).
Awesome, this works! I'm still having an issue, though. While the link_to 'Edit' works, it simply brings up the form for creating a new item. This is my current form:
ReplyDelete<%= form_for ([@character, @character.items.build]) do |f| %>
...
<% end %>
and I thought it might have something to do with @character.items.build, but when I change it to this:
form_for ([@character, @item])
I get this error:
undefined method `model_name' for NilClass:Class
Here's the edit method from items_controller:
def edit
@character = Character.find(params[:character_id])
@item = @character.items.find(params[:id])
end
Also, do I need to define @item in characters_controller? I have @initiative and others defined under the show method, but I'm assuming that's only because I'm listing them in the characters show page, so defining item there won't be necessary.
I see. You've coded yourself into a bit of a corner, but you can still get out; but you're still going to leave to footsteps in the paint.
ReplyDeleteSo, going to /characters/1/items blows up "undefined method 'model_name' for nil":
That's because app/views/items/index.html.erb loads the _form partial, and the _form partial is doing form_for([@character, @item]).
You never defined @item in items_controller#index, so lets do that:
def index
@character = Character.find(params[:character_id])
@item = @character.items.build
end
Refresh that, and now it blows up with "no route matches blah blah..",
that's because app/views/items/_items.html.erb is doing this:
@character.items.each do |item|
...
link_to 'Edit', edit_character_item_path(@character, item)
because we did @character.items.build in the controller, now we have an extra item with no attributes, and cannot be linked to, so the link_to fails.
You can get around this by skipping that one with something like this on line 4 of app/views/items/_items.html.erb
@character.items.reject{|item| item.new_record? }.each do |item|
This omits the "built" item from the list you are looping over, but still allowing it to exist for use in the form.
There are a lot of cleaner ways you could tackle this, like passing locals into your form partial, since you are using it on (at least) index/new/edit and it has to be set up just so whenever it is on the page.
Awesome! Finally, a working edit method! Even if it IS a bit messy. Messy code is what refactoring is for, right?
ReplyDeleteI did run into another problem with the update method for items.
I got this error:
undefined method `item' for #
Taking a look at the update method, I saw that I had this:
@character = Character.find(params[:character_id])
@item = @character.item
So it was pretty clear what was throwing the error. I simply needed to change it to:
@item = @character.items.find(params[:id])
And bingo! Thanks for all your help on this problem. Now that I get how it works, I can extrapolate this method and flesh this application out a bit more.
One weird thing though: now the form isn't saving item.type, either when creating or editing an item. Any idea why a single field wouldn't be saving? The others are working correctly.
ReplyDelete.type is a method already defined by Rails (for use with single table inheritance) so using it as a column name is a bad choice. I typically name my type columns 'somthing_type' or 'kind'.
ReplyDeleteEither rename the column, or if you want to get hacky you can work around it by explicitly setting it like this
@foo[:type] = 'awesome'
@foo.save
But you'll have to do this everywhere, so its advisable to create a migration to rename_column :table_name, :type, :thing_type
then go back and change your forms and partials.
I figured type was a reserved term, but I didn't see it when I looked for a list of them. Changing it won't be a big deal, and I think writing a migration is a better solution than hacking around it. Thanks for the heads up!
ReplyDelete