ActiveRecord - <%= and <% yielding different records

I have a problem of gut-wrenching proportions that can easily be illustrated by the following snippit of template code:

<% member=Member.find(params['id']) %> <p>ID: <%= member.id %></p> <% outfile=File.open('/tmp/look1','w')     outfile.puts(member.id)     outfile.close %>

The correct id is properly displayed within the paragraph element. However, the id that is sent to the disk file is incorrect in that, for some unknown reason, it is the id of the very first record in the database. If I replace '.id' with '.inspect' in the 3ed to the last line, the disk file confirms that it is actually accessing the first record in the DB. I can't figure out why that is. It's like a bad dream from which I expect to awake any moment. I do hope that it hasn't been a long day and that I'm doing something really stupid.

Thanks for any input.

         ... doug

Hi Doug,

I have a problem of gut-wrenching proportions that can easily be illustrated by the following snippit of template code:

For starters, and please don't be offended, but other than the <p>...</p> line, none of this code belongs in your view template.

<% member=Member.find(params['id']) %>

The find belongs in your controller or model and 'member' and its result should be passed 'up' to your view as an instance variable: @member.

It's not possible to tell from the code you've posted, but there are lots of ways you can run into problems with the approach you're taking. The params hash is passed into your controller from a view. It may or may not survive your controller/model processing for use in the next view's rendering.

Also, the use of strings as keys in the params hash has been deprecated for some time; replaced with symbols: e.g., params[:id]. Also, the params hash contains strings. The find method is looking for an integer. So you'll be much safer using params[:id].to_i

<p>ID: <%= member.id %></p>

<% outfile=File.open('/tmp/look1','w')     outfile.puts(member.id)     outfile.close %>

The output to the file system definitely belongs in your controller or view.

The correct id is properly displayed within the paragraph element.

This is most likely an accident.

However, the id that is sent to the disk file is incorrect in that, for some unknown reason, it is the id of the very first record in the database. If I replace '.id' with '.inspect' in the 3ed to the last line, the disk file confirms that it is actually accessing the first record in the DB. I can't figure out why that is. It's like a bad dream from which I expect to awake any moment. I do hope that it hasn't been a long day and that I'm doing something really stupid.

Thanks for any input.

I'd recommend you seperate the code above more appropriately.

Try this for starters. In your controller...

def whatever @member = Member.find(params[:id].to_i) outfile = File.open('/tmp/look1','w') outfile.puts(@member.id) outfile.close end

Personally, for debugging purposes, I'd get rid of the file processing and just to a puts to STDIO; e.g.:

@member... puts "@member.id = #{@member.id}"

It'll save you time.

In your view, keep it to...

<p>I saved a member record with ID = <%= @member.id %></p>

See where that takes you.

HTH, Bill

All of the above suggestions may be valid, however it does not explain how, once the OP has managed by fair means or foul to get hold of the member object that it manages to mutate between his two uses of it.

Colin

Hi Colin,

All of the above suggestions may be valid, however it does not explain how, once the OP has managed by fair means or foul to get hold of the member object that it manages to mutate between his two uses of it.

OK. I want to jump back in here to make sure that Bill doesn't think that he did offend me with his comments. He definitely did not. I welcome all of the help that I can get and Bill's comments are not only valid but contain a lot of help. None the less, I also agree with you, Colin, in that Bill's comments really offer no explanation as to how the object manages to mutate between the two uses of it. What I'm trying to do at the moment is to clean things up by following Bill's suggestions. It's much easier to troubleshoot in the controller than it is in the view. I expect that by using that approach I will ultimately get things to work as desired. Unfortunately, the downside of that approach is that I may never understand why my code (bad as it may have been) didn't work. That's a bit unsettling. Anyway, I continue to work on this issue. I will report back to the list once I have something to report. As always, thanks to all for the help.

          ... doug

All of the above suggestions may be valid, however it does not explain how, once the OP has managed by fair means or foul to get hold of the member object that it manages to mutate between his two uses of it.

OK. I want to jump back in here to make sure that Bill doesn't think that he did offend me with his comments. He definitely did not. I welcome all of the help that I can get and Bill's comments are not only valid but contain a lot of help. None the less, I also agree with you, Colin, in that Bill's comments really offer no explanation as to how the object manages to mutate between the two uses of it. What I'm trying to do at the moment is to clean things up by following Bill's suggestions. It's much easier to troubleshoot in the controller than it is in the view.

In case you did not know it is possible to use ruby-debug in the view by inserting <% debugger %> at the appropriate point, Variables can then be inspected. This assumes you have setup ruby-debug of course.

Colin

Hi Doug,

> All of the above suggestions may be valid, however it does not explain > how, once the OP has managed by fair means or foul to get hold of the > member object that it manages to mutate between his two uses of it.

OK. I want to jump back in here to make sure that Bill doesn't think that he did offend me with his comments. He definitely did not. I welcome all of the help that I can get and Bill's comments are not only valid but contain a lot of help.

I'm glad to see you're back.

None the less, I also agree with you, Colin, in that Bill's comments really offer no explanation as to how the object manages to mutate between the two uses of it.

I'll bet you a nickle that that is not happening. Objects do not 'mutate' on their own. We've been pretty successful at eliminating the old 'branch on occasion' flow control :wink: I'm often reminded of a little ditty I learned in my first CS class.

I hate this damn computer. I think I'm gonna sell it. It never does just what I want, But only what I tell it.

What I'm trying to do at the moment is to clean things up by following Bill's suggestions. It's much easier to troubleshoot in the controller than it is in the view. I expect that by using that approach I will ultimately get things to work as desired.

I believe you will. And if not, it will be much easier for us to help you.

Unfortunately, the downside of that approach is that I may never understand why my code (bad as it may have been) didn't work. That's a bit unsettling.

Given that you're willing to invest the effort to learn to write Rails code, as opposed to code that happens to be 'wrapped' in the Rails framework, I'm more than willing to invest in helping you understand why your previous effort did not work. My guess, however, is that by the time you 'get things to work as desired', you will have figured out on your own why your previous effort did not work.

If not, whenever you're ready, re-post the original code. If you do, please post the entire controller method that renders the view in question along with any filters and library includes / requires you use in the controller, the model, and the view template in its entirety.

Anyway, I continue to work on this issue. I will report back to the list once I have something to report. As always, thanks to all for the help.

Looking forward to hearing back, and you're welcome.

Best regards, Bill

THE GOOD NEWS: I followed Bill's suggestions for cleaning things up and when I finished it all worked! :slight_smile:

THE BAD NEWS: I still have no idea why my original code (bad as it may have been) didn't work.

My guess, however, is that by the time you 'get things to work as desired', you will have figured out on your own why your previous effort did not work.

Unfortunately that didn't happen.

I'm more than willing to invest in helping you understand why your previous effort did not work.

Bill, I really appreciate your willingness to help. I truly believe that we could get to the bottom of it and I also don't really believe that objects mutate between uses. However, it sure SEEMED like that was what was happening. That's why I said that it was like a bad dream. Things like that only happen in dreams. In any event, at this point it would be difficult for me to go back and investigate that old code. I agree with you that there is some explanation. Unfortunately, I just don't know what it is. Since I plan on following your suggestions in the future and avoid writing that kind of code altogether, I'm not sure that it would be worth the effort for us to pursue this. So, with your permission I would just like to leave it as one of life's great unsolved mysteries.

Thanks to all who contributed.

           ... doug

THE GOOD NEWS: I followed Bill's suggestions for cleaning things up and when I finished it all worked! :slight_smile:

Congrats!

THE BAD NEWS: I still have no idea why my original code (bad as it may have been) didn't work.

> My guess, however, is that by the > time you 'get things to work as desired', you will have figured out on > your own why your previous effort did not work.

Unfortunately that didn't happen.

> I'm more than willing to invest in helping you understand why > your previous effort did not work.

Bill, I really appreciate your willingness to help. I truly believe that we could get to the bottom of it and I also don't really believe that objects mutate between uses. However, it sure SEEMED like that was what was happening. That's why I said that it was like a bad dream. Things like that only happen in dreams. In any event, at this point it would be difficult for me to go back and investigate that old code. I agree with you that there is some explanation. Unfortunately, I just don't know what it is. Since I plan on following your suggestions in the future and avoid writing that kind of code altogether, I'm not sure that it would be worth the effort for us to pursue this. So, with your permission I would just like to leave it as one of life's great unsolved mysteries.

As you wish. My personal guess is that it would be a worthwhile effort. We learn more from our mistakes than from our successes. WRT 'difficult to go back', I hope you don't mean that you don't have a source code control system in place. If that is the situation, I strongly recommend you invest the time to correct that. Github is free. SVN is free. Being unable to revert to a prior code state can be very, very expensive.

Best regards, Bill

Guess what? IT'S BACK! A variant of the problem has re-surfaced. I'll try to get something together tomorrow to illustrate the issue. Of course, now that I have things better organized, I may find the solution in the process. One way or another, I'm hoping that we'll be able to get to the bottom of this.

Thanks.

          ... doug

OK. Here's my controller code (line numbers assigned for convenience):

1 def update() 2 outfile=File.open('/tmp/look1','w') 3 outfile.puts(params.inspect) 4 outfile.close 5 @member=Member.find(params[:id].to_i) 6 outfile=File.open('/tmp/look2','w') 7 outfile.puts(@member.inspect) 8 outfile.close 9 profile=Member.profile(@member.flags) 10 outfile=File.open('/tmp/look3','w') 11 outfile.puts(profile.inspect) 12 outfile.close 13 # @profile=profile 14 end

As you can see, aside from the code which writes crucial data to respective disk files for debugging purposes, there are really only 3 lines of code in the controller, line 5, line 9, and line 13 which is commented out. You may have noticed that lines 9 and 13 can easily be combined into a single line. I broke them apart to make clear that the problem is not in the model code.

I invoke the code by entering 'http://my_host/members/update/15' into my browser.

With line 13 commented out, everything seems to work perfectly. Under that condition, here is what the contents of my debugging files looks like:

$ cat /tmp/look1 {"action"=>"update", "id"=>"15", "controller"=>"members"} $ cat /tmp/look2 #<Member id: 15, login: "bob1", password: "ppss", flags: 2, first_name: "Bob", last_name: "Chaput", email: "bob@rchapix.com", courtesy_title: "Mr.", created_at: nil, updated_at: "2009-08-09 23:40:18"> $ cat /tmp/look3 [["1", "Members List"], ["0", "Other List 1"], [0, "Other List 2"]]

The above results are exactly what is expected.

Where the trouble arises is when I uncomment line 13 and pass the profile data to the view via the @profile instance variable. When I do that, things get all screwed up. Here is the output of the debugging files under that circumstance:

$ cat /tmp/look1 {"format"=>"gif", "action"=>"update", "id"=>"SpryMenuBarRightHover", "controller"=>"members"} $ cat /tmp/look2 #<Member id: 0, login: "admin", password: "secret", flags: 0, first_name: "Admin", last_name: "Admin", email: "foo@bar.com", courtesy_title: "Mr.", created_at: nil, updated_at: "2009-08-09 18:03:42"> $ cat /tmp/look3 [["0", "Members List"], [0, "Other List 1"], [0, "Other List 2"]]

So, somehow passing the profile data to the view via the @profile instance variable causes things to get screwed up. It appears that the cause of the problem is that for reasons unknown to me, the params are now totally wrong. That's the culprit. I just don't know what's causing it. Any ideas?

Although I'm not sure what it has to do with the issue, here is my relevant view code:

<select name="flags" multiple="multiple" size="4" style="display: block; margin-left: auto; margin-right: auto;">   <% index=0         value=1         @profile.each do |option| %>   <%= "<option value=\"#{value}\" #{"selected='selected'" if option [0].to_i==1}>#{option[1]}</option>" %>   <% index+=1         value=2*value         end %> </select>

Thanks for any input. I'm totally at a loss:

          ...doug

1 def update() 2 outfile=File.open('/tmp/look1','w') 3 outfile.puts(params.inspect) 4 outfile.close 5 @member=Member.find(params[:id].to_i) 6 outfile=File.open('/tmp/look2','w') 7 outfile.puts(@member.inspect) 8 outfile.close 9 profile=Member.profile(@member.flags)

Without seeing your models this is a WAG, but is #profile really a class method? If a Profile belongs to a Member, I'd expect to see something like

            profile = @member.profile

10 outfile=File.open('/tmp/look3','w') 11 outfile.puts(profile.inspect) 12 outfile.close 13 # @profile=profile 14 end

FWIW,

Thanks for the input. I *THINK* that I have it right; but, just to avoid any possible confusion, here is my relevant model code:

class Member < ActiveRecord::Base   def self.profile(decimal_profile)     profile=['Members List','Other List 1','Other List 2']     binary_profile=decimal_profile.to_s(2).split(//)     outArray=Array.new     profile.length.times do |index|       outArray[index]=[binary_profile.fetch(index,0),profile[index]]     end     return outArray   end end

[Taking things slightly out of order...]

Thanks for any input. I'm totally at a loss:

Me too. At a loss, that is. So let me make sure I understand...

Where the trouble arises is when I uncomment line 13 and pass the profile data to the view via the @profile instance variable. When I do that, things get all screwed up. Here is the output of the debugging files under that circumstance:

Let me make sure I understand what you're doing \ expecting via the request \ response cycle as it applies to Rails.

The cycle starts when a request is made from the browser to a method in a controller in the Rails app. In the simplest case, the controller grabs data via its associated model and then passes that data via instance variables to the associated view templates to render a response page.

Where the above doesn't make sense to me is that it implies that your update() method is called from some page, renders a new page which then calls it again. Is that right? If not, which cycle is the view containing the select below involved in? Is it feeding update(), or being generated via update() ?

OK. Here's my controller code (line numbers assigned for convenience):

1 def update()

What is the purpose of the parens above?

2 outfile=File.open('/tmp/look1','w') 3 outfile.puts(params.inspect) 4 outfile.close 5 @member=Member.find(params[:id].to_i) 6 outfile=File.open('/tmp/look2','w') 7 outfile.puts(@member.inspect) 8 outfile.close 9 profile=Member.profile(@member.flags) 10 outfile=File.open('/tmp/look3','w') 11 outfile.puts(profile.inspect) 12 outfile.close 13 # @profile=profile 14 end

As you can see, aside from the code which writes crucial data to respective disk files for debugging purposes, there are really only 3 lines of code in the controller, line 5, line 9, and line 13 which is commented out. You may have noticed that lines 9 and 13 can easily be combined into a single line. I broke them apart to make clear that the problem is not in the model code.

I invoke the code by entering 'http://my_host/members/update/15' into my browser.

What do you mean 'entering'? An update should be a POST or better yet a PUT of a params hash that would come from a form. I don't understand 'entering'.

With line 13 commented out, everything seems to work perfectly. Under that condition, here is what the contents of my debugging files looks like:

$ cat /tmp/look1 {"action"=>"update", "id"=>"15", "controller"=>"members"} $ cat /tmp/look2 #<Member id: 15, login: "bob1", password: "ppss", flags: 2, first_name: "Bob", last_name: "Chaput", email: "bob@rchapix.com", courtesy_title: "Mr.", created_at: nil, updated_at: "2009-08-09 23:40:18">

This is an inappropriate use of the update method. Should be using 'show' here since all you're doing is GETting information about the member.

$ cat /tmp/look3 [["1", "Members List"], ["0", "Other List 1"], [0, "Other List 2"]]

The above results are exactly what is expected.

Where the trouble arises is when I uncomment line 13 and pass the profile data to the view via the @profile instance variable. When I do that, things get all screwed up. Here is the output of the debugging files under that circumstance:

$ cat /tmp/look1 {"format"=>"gif", "action"=>"update", "id"=>"SpryMenuBarRightHover", "controller"=>"members"} $ cat /tmp/look2 #<Member id: 0, login: "admin", password: "secret", flags: 0, first_name: "Admin", last_name: "Admin", email: "foo@bar.com", courtesy_title: "Mr.", created_at: nil, updated_at: "2009-08-09 18:03:42"> $ cat /tmp/look3 [["0", "Members List"], [0, "Other List 1"], [0, "Other List 2"]]

So, somehow passing the profile data to the view via the @profile instance variable causes things to get screwed up. It appears that the cause of the problem is that for reasons unknown to me, the params are now totally wrong. That's the culprit. I just don't know what's causing it. Any ideas?

Although I'm not sure what it has to do with the issue, here is my relevant view code:

What do you mean 'relevant'? What's the name of this template? And, at a minimum, post the <%= form... %> line so we can understand the flow.

<select name="flags" multiple="multiple" size="4" style="display: block; margin-left: auto; margin-right: auto;">   <% index=0         value=1         @profile.each do |option| %>   <%= "<option value=\"#{value}\" #{"selected='selected'" if option [0].to_i==1}>#{option[1]}</option>" %>   <% index+=1         value=2*value         end %> </select>

Tell us about the other view templates and controller methods that are involved here. Also, please post your Member model.

Best regards, Bill

I just wanted to mention that I think that this issue has boiled down to, "Why are the params different?" When we know that, I think that we'll have the answer. Thanks again.

            ... doug

The params hash get passed from the view to the controller. So the question is... you've shown a controller method. Is that method being called from the view you showed? Or generating the values passed to the view template?

Some of your post implies the former, while others imply the latter. Please post the view template that calls the method as well as the view template that's rendered by the method. The former would typically be edit.rhtml while the latter whould be update.rhtml. But post whatever you're actually using. The request / response flow is what we need to understand to help you.

Best regards, Bill

Thanks for the input. I *THINK* that I have it right; but, just to avoid any possible confusion, here is my relevant model code:

Yeah, I'm pretty sure that's not what you want :slight_smile:

Consider the following:

ripple:~$ irb

class Foo def self.profile   "something" end def profile   "otherwise" end end

=> nil

foo = Foo.new

=> #<Foo:0x1258040>

Foo.profile

=> "something"

foo.profile

=> "otherwise"

You'd probably do well to open up a console and try experimenting directly with Member.profile and variations. Or at least, that usually helps me get my thinking around a problem.

:fwiw

The params hash get passed from the view to the controller.

Really? I'm definitely thinking the other way around. Let's think about this for a minute. In the URL box of my browser I enter 'http:// my_host/members/update/15' and press enter. I would expect this request to be routed to the update action of the members controller and I would expect there to be an :id=>'15' entry available in params for that action to use along with an :action=>'update' entry and the default update view which is the view that I showed. As far as I know, that's exactly what happens when I have the @profile=profile line in my controller commented out. However, when I uncomment that line, for reasons unknown to me, I get a totally different set of parameters available in my action even though I used exactly the same URL in my browser. It blows my mind! :slight_smile:

So, to answer your question:

you've shown a controller method. Is that method being called from the view you showed? Or generating the values passed to the view template?

It is the later, i.e., the controller method generates the values passed to the view template.

The request / response flow is what we need to understand to help you.

I hope that my response in this post has clarified the issue. If not, please let me know. Thanks.

           ... doug

You'd probably do well to open up a console and try experimenting directly with Member.profile and variations.

Well, unfortunately, I have already done that. Member.profile seems to behave exactly as expected in a variety of cases tested. Also, if you look at the debug data you can see that the corruption occurs long before Member.profile is even called. That's what makes this so puzzling. The params die is cast; but, the result of casting that die is determined by something that happens much later. Is this voodoo or what? Thanks.

         ... doug

> The params hash get passed from the view to the controller.

Really? I'm definitely thinking the other way around. Let's think about this for a minute.

OK.

In the URL box of my browser I enter 'http:// my_host/members/update/15' and press enter. I would expect this request to be routed to the update action of the members controller and I would expect there to be an :id=>'15' entry available in params for that action to use along with an :action=>'update' entry and a :controller=>'members' entry.

That is correct. All three 'entries' are members of the params hash. I should have worded my statement more carefully. The params hash is passed from the page rendered by the view in the *last* request / response cycle, to the controller / method as the start of the *current* request / response cycle. Instance variables get passed from the controller to the view templates in the current request / response cycle to render the response page. I apologize if my use of the word 'view' confused.

The action does its thing and renders the default update view which is the view that I showed. As far as I know, that's exactly what happens when I have the @profile=profile line in my controller commented out.

Yes.

However, when I uncomment that line, for reasons unknown to me, I get a totally different set of parameters available in my action even though I used exactly the same URL in my browser. It blows my mind! :slight_smile:

Commenting or uncommenting that line has nothing at all to do with what the controller is receiving in the params hash from a request initiated via a page rendered in the last cycle. It has only to do with what the view template will render in the current cycle. And if, in fact, the view you showed is update.rhtml then that view will throw an error on nil.each if you comment out line 13. You haven't mentioned that. Is it happening or not?

This is too hard to follow in fits and spurts. Zip your app up and send it to me offline. I'll take a look and get back to you with the explanation.

Bill bwalton dot im at gmail dot com