Rails Method not available in View - ruby-on-rails

I get this strange error. I have defined a method for my model
class Rating < ActiveRecord::Base
[...]
belongs_to :user
belongs_to :movie
[...]
def to_text
texto = case self.grade
when 0..1 then "horrible"
when 2..3 then "bad"
when 4..5 then "not bad"
when 6..7 then "good"
when 8..9 then "very good"
when 10 then "master piece"
end
end
end
Then, on my controller, I define this instance:
#current_user_rating=#movie.ratings.where(:user_id => current_user)
And it does find it, so it works. But then, when I call the method, or a property like
<%= #current_user_rating.grade %>
<%= #current_user_rating.to_text %>
I get this error
undefined method `grade' for []:ActiveRecord::Relation
undefined method `to_text' for []:ActiveRecord::Relation
Why the variable not behaving as an instance with appropriate attributes and methods but as a Relation?
It does work on the Console, but not on the server...

since a movie has multiple ratings, #current_user_rating is a collection of them, even if there is one. you should be able to get rid of the errors by calling your methods like this:
<% #current_user_rating.each do |rating| %>
<%= rating.grade %>
<%= rating.to_text %>
<% end %>

When you call where it doesn't actually query the database, it creates an object which can be converted and run on the database. This is useful for further modifying the query before it is run.
Typically a further call to first or all is added to actually execute the query and get the result. (Note that this might be done automatically if you call where in the rails console, so that the result can be printed)
In your case, it looks like a user is expected to rate a movie no more than once, so the following seems appropriate:
#current_user_rating=#movie.ratings.where(:user_id => current_user).first
EDIT: Actually I think GSto is correct, what I wrote above does not look like the reason for your failure. The error message is actually complaining that the methods you are trying to call aren't valid to be called on an Array of objects. In this case, first is simply selecting the first result and ignoring any others.
It would be good to double-check if you have any movies for which a single user has multiple ratings. In fact, I'd recommend adding a validation to ensure that this is not allowed, if it is not intended.

Related

Showing the email of the person to whom I sent a comment

I use the following code to show the email of the person to whom I sent a comment.
Controller:
#to_user = User.where(id: #comment.to_id)
View:
<%= #to_user.email %>
Only it generates an error: undefined method `email' for #<User::ActiveRecord_Relation:0x007feb364b8218>
Showing "from" user works well:
<%= #comment.user.email %>
Not sure what I am doing wrong. Help would be much appreciated.
To retrieve a single object, you use find:
#to_user = User.find(#comment.to_id)
But it's more idiomatic to define a belongs_to association in your Comment model, e.g.:
class Comment < ActiveRecord::Base
belongs_to :to_user, class_name: 'User', foreign_key: 'to_id'
# ...
end
This allows you to simply write:
<%= #comment.to_user.email %>
To avoid confusion, you might want to rename the exising user to from_user. Or maybe use more descriptive names like sender and receiver.
And if you do so, it would be a good idea to also rename the columns from user_id and to_id to from_user_id and to_user_id (or sender_id and receiver_id).
.where returns an ActiveRecord_Relation, which you can think of like an array of records (actually, the records aren't loaded yet, but as soon as you call a method like .each or .to_a it evaluates the query and turns it into an array).
What you are looking for is either
.find (User.find(#comment.to_id)) - this will raise an error if the user is not found
.find_by (User.find_by(id: #comment.to_id)) - this will return nil if the user is not found
You could also just call .first on the .where result - as often is the case in Ruby/Rails, there are many ways to do the same thing.

Rails Creation through association & Collection vs. Array

I am confused about the some Association concepts in Active Records.
I have three models User, Bank and Bankaccount. Both the User and the Bank models "has_many" Bankaccounts and the Bankaccount model "belongs_to" both the User and the Bank models. I use the following syntax to create a Bankaccount through its association with User
#bankaccount = #user.bankaccounts.create(bankaccount_params)
What is the appropriate syntax if I want to create a bankaccount object through both the association with User and the association with Bank?
My second question is related to this one. Right now, because I am not sure how to create a bankaccount through both associations, I handle the association with the Bank by putting the parameter manually
bank_id = params[:bank_id]
However, this seems to trigger some issues down the road when I want to iterate through all the bankaccounts and retrieve the name of the associated bank.
In my view I have
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank %>
I obtained a list of these
#<Bank:0x007f7a66618ef0>
#<Bank:0x007f7a664c9ab8>
If I tried to get the name of the bank
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank.name %>
I get an undefined method name for nil class. I do get the name of the bank in the console with these simple lines
bankaccount = Bankaccount.find(1)
bankaccount.bank.name
Could you anyone give me more background on those concepts and provide me with the appropriate syntax to loop accross my collection #user.bankaccount and for each bankaccount retrieve the name of the associated bank?
Thanks.
You'll have to choose one association to create a bankaccount through, then set the second separately:
#bankaccount = #user.bankaccounts.new(bankaccount_params)
#bankaccount.bank = somebank
#bankaccount.save
Or
#bankaccount = #bank.bankaccounts.new(bankaccount_params)
#bankaccount.user = someuser
#bankaccount.save
In addition, I don't see why setting the second association manually with a param would inherently cause the other problems you are experiencing. This should be fine (assuming a bank with this id actually exists):
#bankaccount.bank_id = params[:bank_id]
If you choose to assign a foreign key as a parameter, you can roll it into strong parameters and pass it into the bankaccount model with everything else. For example:
def bankaccount_params
params.require(:bankaccount).permit(:bank_id, ...)
end
You last issue regarding arrays vs. collections depends on what you are trying to do. First, if you are particularly interested in the bankaccount's bank name, make it easier to get:
class Bankaccount
belongs_to :bank
...
def bank_name
bank.name
end
end
For those who buy into such things, this also prevents a Law of Demeter violation.
If you are simply trying to list the names of banks for #bankaccounts in a view, try leveraging Rails partials with something like this:
app/views/bankaccounts/index.html.erb
<%= render #bankaccounts %>
app/views/bankaccounts/_bankaccount.html.erb
<%= bankaccount.bank_name %>
More on this here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
If you're looping over #bankaccounts for another reason, the code you provided should work, given that #bankaccounts represents ActiveRecord relations and not a simple array:
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank_name %>
<% end %>
Since you're getting an undefined method error, your problem probably stems from how you are building #bankaccounts. If you are doing exactly this...
#bankaccounts = #user.bankaccounts
...and you've verified that everything is properly associated in the console, then your problem is likely unrelated to arrays or collections.

Can I use an instance variable when chaining methods?

I have a show page for the periods controller. What I want to do is show all the registrations for that period.
In my period model I have: has_many :registrations, and in my registration model I have belongs_to :period
What I want to do is something like this:
<% registrations.#period.each do |registration| %>
<% end %>
That doesn't seem to work though. How can I make this work? and not get the error syntax error, unexpected tIVAR
registrations.#period.each do |registration|
No, you can't do this. Instance variables are always private, you cannot access them from outside the instance. The only way to interact with an object is by sending it messages.
So, if you want to access an instance variable from the outside, you need to define a getter method for it.
Thankfully, belongs_to :period already defines a getter method called period for you, so you can use that.
I think what you might want is
<% #period.registrations.each do |registration| %>
<% end %>
In general, if you really want to access instance variables, you can use the instance_variable_get method. Yet, in your example it won't work because a) you were trying to call it on an Array and b) associations (belongs_to) don't create instance variables.

Ruby : Access array object by index vs iterator

I'm pretty new to Ruby and the Rails framework. My background is primarily Java. Anyhow, I'm facing a weird situation. I have a method in one of my models that returns associated models. The association is as follows. A has_many Bs, and B belongs to A (i.e. one-to-many)
class ModelA < ActiveRecord::Base
has_many :model_bs
def get_bs
ModelB.where(:a_id => id)
end
end
class ModelB < ActiveRecord::Base
belongs_to :model_a
end
In my view, if I try to access the records (models) in the result set, I'm able to call its properties without any issue (Figure A). Life is good.
Figure A:
<% bs = a.get_bs %>
<% bs.each do |b| %>
<%= b.some_prop %>
<% end %>
But if I try to access the models by index, I get an error saying that I can't call a method on a nil object (Figure B & C).
Figure B:
<% bs = a.get_bs %>
<%= bs[0].some_prop) %>
Or even..
Figure C:
<% bs = a.get_bs %>
<%= bs[0].first %>
Does not work. I know it's user error (me). I've looked at the documentation for accessing objects from a collection (in this case, I believe it's a Ruby array). I've also searched here on StackOverflow. I'm still left scratching my head. I haven't quite found a similar thread.
You are wrong, it is not Array, it is an ActiveRecord::Relation class. You can transform it to an array with .to_a, if you really need it. I've checked, you can use [] operator to access item by index: ModelA.where("created_at = created_at")[0].name, so I think the problem is somewhere else, maybe in your condition.
Check the documentation.
But anyway, you shouldn't use the relationship like this. Use has_many and belongs_to to indicate relationship between models. Like this:
class ModelA < ActiveRecord::Base
has_many :ModelB
end
class ModelB < ActiveRecord::Base
belongs_to :ModelA
end
I found a solution. It's not pretty but it'll due for now. With a little help from someone on LinkedIn, I discovered that using the .try method on my model while attempting to access an attribute, I'm able to retrieve the value without the null pointer exception.
Example:
<% bs = a.get_bs.to_a%>
<%= bs[0].try(:some_attr) %>
It's not clear to me as why I need to use the .try method. I mean, I know what the method is for. It's a convenience method for checking nil values and allowing the page to render without throwing an exception. But it's obvious that my model is not null and it has data. So why am I only able to access its attributes with the .try method? Honestly, I think this could be a bug in Rails.
I think what I'll end up doing is create a helper method that utilizes the .try method. That way, I'm not calling .try all over my views.
Why not using this?
def get_bs
ModelB.find_all_by_a_id(id)
end

has_many through and partials

I have a User model, a Post model, and an Interest model.
User has_many posts through interests
User has_many interests
Post has_many users through interests
Post has_many interests
Interest belongs to Post
Interest belongs to User
Application_Controller is as follows:
class ApplicationController < ActionController::Base
before_filter :login_from_cookie
before_filter :find_user_interests
helper :all # include all helpers, all the time
session :session_key => '_blah_session'
include AuthenticatedSystem
def find_user_interests
#user_interests = current_user ? current_user.interests : []
true
end
end
Application.html.erb has as follows:
<%= render :partial => "users/interests", :object => #user_interests %>
_interests.html.erb partial is as follows:
ul
<% unless current_user.nil? then -%>
<% #user_interests.each do |interest| -%>
li<%= interest.post.title %>/li
<% end %>
<% end -%>
/ul
Given all this when I at localhost:3000/posts/1 my partial shows up fine, but when in localhost:3000/posts I get an error undefined method 'title' for nil:NilClass thus an error in the line li<%= interest.post.title %>/li shown above in the _interests.html.erb partial.
What the heck would be the issue?
TIA
That just means that one of the interests doesn't have an associated post on the other end. Most likely it was deleted. This could have been prevented by the following:
class Post < ActiveRecord::Base
has_many :interests, :dependent => :destroy
end
In the meantime you should clean up the orphans in the database.
Edit: You claim this was already in your model, but if it was then it's not clear how you could have an orphaned Interest as the error indicates. Maybe it was created before you added the dependent clause? Again, go delete the orphans via SQL and then try again. If the problem resurfaces later you must be deleting without callbacks somewhere.
Regarding your size problem. You could be using current_user.interests.count. This is due to some magic with Rails associations. count is a special method on a Rails association that runs SQL. length is just an array method telling you how many items are in the array. Rails associations have a few special methods, but the rest of them they pass through to the array object transparently.
Further critiques: when you pass :object => #user_interests you are setting a variable with the name of the partial. So you could reference the local variable interests in the partial. However you are referencing #user_interests, so passing the object is not necessary. All else being equal, passing the object and using a local variable is probably better (it's more explicit, more of a functional-programming style), but in this case you are not making use of that.
Finally, stylewise, I may be wrong since I don't have the full context, but in general I would put the logged_in condition in the template rather than setting user_interests to an empty array if there is no logged in user. This would allow you to reference current_user.interests.count in the template and independently set the interests to be displayed (eg. for pagination) in #user_interests.

Resources