Extra instance variable in controller is nil in view? - ruby-on-rails

I have been searching through Stack Overflow for a few hours now, but none of the related questions seem to apply to my issue.
I am new to Rails, with this being my first real project, and I may be confusing the MVC setup a little. I am attempting to assign the #stars instance variable while in an action of the searches_controller.rb:
def create
#search = Search.new(params[:search])
tempstr = searchstr(#search)
#stars = Star.where("tempstr", :limit => 100)
end
#search is created fine, being a complex search with varying parameters. tempstr is just a simple string container for the results of searchstr(#search), which is a quick method for converting the search parameters into a MySql-relevant string (which seems to be easier than trying to use the .where helper normally, in this case). I'm sure I can just put searchstr(#search) directly into the .where, but I split them up for now so I can inspect the elements as they pass through.
Anyways, the issue comes up when I try to call #stars in the show.html.erb view. Even with something as simple as this:
<% #stars.each do |star| %>
<%= display stuff %>
<% end %>
I get an error saying 'each' is not a method of nil:NilClass. So, I changed it to the following to see if #stars was nil:
<%= #stars.inspect %>
Sure enough, #stars is nil. However, when I add this line to my controller to check #stars there:
return render #stars.each
I see that the variable is filled with the correct star objects from the Star.where(), just as I had intended. A quick .inspect shows the variable is not nil, when in the controller.
So, I am unsure why the view is receiving it as nil if it has been defined in the controller just fine. I wouldn't be surprised if it was me misunderstanding how MVC works, though. The Star class was defined in the Star model, but maybe it is because I am trying to access it from the Searches controller, and thus it isn't initialized for the view?
Should I be going about doing this some other way? I attempted to use a local variable (using stars instead of #stars), but then the view says "Undefined local variable or method 'stars'".
Any help would be much appreciated, I have already wracked my brain for hours creating the complex search and parsing the star file data into the database, so I'm a bit burnt out. I can supply more information if requested, I'm not sure what else would be helpful in providing an answer.

You are setting #stars in the create method, but the view you are talking about is show.html.erb. Try setting #stars in the show method too. Something like this:
def show
#search = Search.find(params[:id])
tempstr = searchstr(#search)
#stars = Star.where("tempstr", :limit => 100)
end
If this does not help you, please show the rest of you controller actions, so we can help you better.

Related

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

Rails Controller Confusion

this is my first post.
I'm brand new to Rails and I'm attempting to learn how to use it. To be clear, I have a brushing familiarity with Ruby. I'm pretty sure I get the MVC structure, but I'm having trouble understanding certain behaviors I'm experiencing.
Just in case anyone learned from the same source, I'm watching Derek Banas explain it. He explains the thing I'm having trouble with around 16:20. https://www.youtube.com/watch?v=GY7Ps8fqGdc
On to specifics- So I placed this line in my routes.rb file:
match':controller(/:action(/:id))', :via => :get
and I created an instance variable in the controller using this:
def sample
#controller_message = "Hello From The Controller"
end
And in a sample view I created, I call on the "controller_message" variable like this:
<%= "#{#controller_message}" %>
And it works on that one view half the time. Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right? Maybe I just don't understand how this functions, but I made other view files in the same directory in an attempt to see how controllers pass data to views. They load and everything, but I'm not getting the message from the controller. Sometimes, seemingly inconsistently, the controller message won't even display on the first view where it worked originally, especially if I navigate around the site a little. To get it to display that message again, I have to restart my server.
So am I just misunderstanding how MVC works, or is my software glitching (unlikely, I know), or what? I'm so confused.
I've heard so many great things about this community. Thanks in advance to anyone willing to help me. I'm so stressed out.
The #{} in <%= "#{#controller_message}" %>is string interpolation. The usual convention of displaying an instance variable in a view is simply <%= #controller_message %>
The variable #controller_message, declared in the sample method, makes that variable available to the view associated with that method. By default, rails will look for a corresponding view file that has the same name as the controller method, so in this case it will look for a view called sample.html.erb in the app/views/your_controllers_name folder
Well, to clarify things (because your question is a little vague): rails does a lot of stuffs behind the scene, like relating controller's name with view's name. That is why, in most case, you don't invoke a view to be render in controller's method (Rails does that to you based on file's name). So, would make sense the variables declared in a controller method be displayed only in the view with the same name as the controller method called. If you want to display a variable in a view that is not related with the controller method, you should invoke that view with methods like Render and Redirect, and pass the variables as arguments to those methods.
I.g:
other.controller
def edit
render "/another_view_folder/example.html.erb", #variable_to_be_displayed
end
Another thing:
<%= #controller_message %>
This is enough to display the variable. The way you were doing was Interpolation (use to concatenate variable with strings).
I hope I can help you!
Rails will implicitly render a view file if it is found in the view path.
So given:
# config/routes.rb
get 'foos/bar'
# app/controllers/foo_controller.rb
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
end
end
Rails will attempt to find the file bar.html.erb in app/views/foos when we request /foos/bar. If we want to render another view we need to tell rails to render it explicitly.
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
render 'some_other_view' # renders app/views/foos/some_other_view.html.erb
# or
render 'some_other_folder/some_other_view' # renders app/views/some_other_folder/some_other_view.html.erb
end
end
Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right?
No. Lets say you add another controller and view.
# config/routes.rb
get 'people', to: 'people#index'
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
def index
end
end
# app/views/people/index.html.erb
<h1>The message is: <%= #controller_message %></h1>
It will just render The message is:. Since PeopleController does not set the instance variable #controller_message. Ruby will not raise an error if you reference an unassigned instance variable.
The way to reason about this is that the controller in Rails packages all its instance variables and passes them to the view context so that we can use them in the view.
After the controller has finished rendering it sends the rendered template and the program exits*. Instance variables, local variables etc, do not carry over to the next request.

Elegant way to show corresponding text_field_tag value in Rails 4

So my problem is showing something that a model has in a nice and simpler way.
So what currently works?
In my viewer this works fine:
<%= text_field_tag(:first_name, (current_user.present? ? current_user : '').first_name.present? ? current_user.first_name : '') %>
However this is too long and really hard to maintain, especially when I have several more fields.
So to avoid that I made this in my controller
def user_vals(value)
if(current_user.present?)
current_user.value.present? ? current_user.value : ''
end
return ''
end
Within this controller I can call user_vals(:first_name) but get undefined methodvalue'` error. Furthermore I cannot just call
<%= text_field_tag(:first_name, #user_vals(:first_name)) %>
As I am getting some syntax error with brackets but that's not the real issue.
So my ultimate goal is to have something like this:
<%= text_field_tag(:first_name, #user_vals(:first_name)) %>
Rather than the first code I've given above. How can I achieve that?
You can use try in this case. Just write:
<%= text_field_tag(:first_name, current_user.try(:first_name)) %>
See: http://apidock.com/rails/Object/try
I would recommend to take a step back and try to use Duck Typing to solve this...
What you have right now is a current_user method. This method can return whatever object it wants if the user is not logged in, and that object can respond to whatever it wants. Here's a simplified example:
def current_user
#user || OpenStruct.new(first_name: "")
end
Note: I'm assuming #user holds the currently-signed in user... but this may be a call to super instead, or whatever else depending on your implementation.
So now, instead of branching based on what type of object is coming back from the current_user method, you can now just use the returned object without regard.
current_user.first_name # => Either the User object's first name or `""`
You can go further with this by creating e.g. a GuestUser class and having GuestUser.new returned instead of the OpenStruct above. Guestuser would be a model that is not data-base backed and could respond to any number of methods as needed.
This idea has been represented by many others as well. And using a class to prevent repeated code switching based on nil actually has a name: The Special Case Pattern. As a quick, free example, see the Guest User RailsCast. Or, if you subscribe to Ruby Tapas, be sure to check out episode 112: Special Case. This topic, and many others, are also covered in depth in Avdi Grimm's excellent book: Confident Ruby, which I highly recommend.

Instance variable not being recognized by its view

Can anyone think of a reason why an instance variable declared in the controller would not be recognized by its view?
Controller looks something like this:
class UsersController < ApplicationController
...
def show
#questionshow = session[:profilequestioncapture]
#user = User.find(params[:id])
#question = Question.where("id = ?", #questionshow).first
...
And the view: (show.html.erb in the users directory:)
...
<dl class="dl-horizontal">
<dt>Headline</dt>
<dt>Description</dt>
<dt>Budget</dt>
<dd><%= #question.headline %></dd>
<dd><%= #question.description %></dd>
<dd><%= #question.currency %>&nbsp<%= #question.budget %></dd>
</dl>
....
-the session correctly populates the #questionshow instance variable. It only contains the id, and this gets correctly passed to #question.
-here's what's strange: <%= #user.xxxx %> gets correctly formatted and displayed, whereas anything with <%= #question.xxx %> does not.
-and here's what's even stranger. If I comment out #user in the controller the record is still correctly displayed in the view, so it's effectively ignoring what's in the controller (but I can't figure out why)
-And yes I've checked I'm looking at the right controller & viewer.
Are you sure that session[:profilequestioncapture] is not nil?
#question is an ActiveRecord::Relation object. unless you call .first on it, you'll have errors when you try to call some of the question attributes to it. and that will also be true if, as stated by one of the answers, session[:profilequestioncapture] is nil
First I think that the where scope will give you an ActiveRecord::Relation (which gives an array), not a record. Since you want to find by id I would suggest
Question.find(session[:profilequestioncapture])
Most probably you will want to rescue ActiveRecord::NotFound exception and either set a default question or change the behavior.
Are you using Draper or Presenters?
Can you double check your #question actually has data?
As for your #user behind populated even if commented out:
Check for caching issues
Check for before_filter actions that could populate it for you.
I found the solution, but it's left me a bit bewildered. I was not defining the same instance variable attached to my Questions model across all actions in the same controller. So in Show is was #question whereas in Create it was (for example) #yyyquestion and in Update I had used something else (e.g. #yyyquestion). I have no clue why this would cause the error and why the instance variable would be populated in the controller but not in the View. I think there is more to this story...

Passing a model attribute to a Rails view using redirect_to

I'm trying to pass a model attribute to a view, after successfully setting it to a new value from inside an action in my controller. But this variable is always nil by the time it gets to the view, so I can't use it to conditionally display stuff. I should add that this attribute is not a field in the database. What am I missing/doing wrong?
Here is the code in my model:
attr_accessor :mode
#getter
def mode
#mode
end
#setter
def mode=(val)
#mode = val
end
...in the controller:
#report.mode = "t"
redirect_to edit_report_path(#report)
...and in my view:
<%= build_report(#report.mode) %>
...but this helper method never gets the variable I just set in the controller. It is nil. What gives? Clearly I'm missing something basic here because this seems like it should be straightforward. Any insight would be greatly appreciated.
Thanks.
edit_report_path generates a URL with the ID of #report in it.
redirect_to essentially creates a whole new request, and goes to that URL. When it gets to edit, all it has is the ID. Usually that's fine - it looks up the object and keeps going, but of course it's not going to have the non-db field you set.
There are a couple ways to fix this. You can use :render instead to get to the edit page - then #report will have the field set.
#report.mode = "t"
render :action => edit and return
Or, you can make mode a database field.
The problem here is in the redirect_to. When you redirect somewhere else all instance variables are lost. So when you set #report.mode = "t" it sets the attribute. But when you redirect that data is lost.
I am assuming the <%= build_report(#report.mode) %> is in edit_report.html.erb and the code from when you set 'mode' is not in the edit action. If this is the case you may be able to pass the report.mode to the edit action in the url like so:
build_report(#report.mode, :mode => "t")
The problem is the redirect_to; you're returning a response to the client that causes it to redo the request with a different url. In that second request the mode isn't set because you didn't save it before wrapping up the first request.

Resources