Can I use an instance variable when chaining methods? - ruby-on-rails

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.

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.

How to reduce number of calls to a helper method

In my view, I need a User object to display a few different properties. There is an instance variable #comments that's being sent from the controller. I loop through the comments and get the User information through a helper method in order to reduce db calls.
Here is the helper method:
def user(id)
if #user.blank? == false && id == #user.id
return #user
else
return #user = User.find(id)
end
end
And in the view, I display the details as follows:
<h4> <%=user(comment.user_id).name%> </h4>
<p><%=user(comment.user_id).bio%></p>
<p><%=user(comment.user_id).long_bio%></p>
<p><%=user(comment.user_id).email%></p>
<hr>
<p><%=user(comment.admin_id).bio%></p>
<p><%=user(comment.admin_id).long_bio%></p>
<p><%=user(comment.admin_id).email%></p>
I was told that assigning a variable in the view is bad practice and hence I am calling the helper method multiple times instead of assigning the returned User object.
Is there a better way to do this?
I think you are overcomplicating things here.
Let's say you have a user model
class User < ActiveRecord::Base
has_many :comments
end
an admin model
class Admin < User
end
a comment model
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you only need a type column in your users table and you can do things like this:
Admin.all (All users with type "Admin")
User.all (Really all users including type "Admin" and all other types)
and for every comment you can just use
comment.user.bio
and it doesn't matter if it's an admin or not.
See http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails for example
Additional info: To reduce db calls in general(N+1 queries) watch http://railscasts.com/episodes/372-bullet
It's perfectly fine to pass models to your view and build the data on the view off of the data contained in the model. Keep in mind that I'm not entirely certain how you want your page to work, but one option you may have is to use a partial view and pass it the user object. This allows you to still only have the one model in your partial view without setting additional variables.
Also, without knowing what kind of database you're using or if your models have any associations, and assuming that you're doing some input validation, you may not need this helper method and may be able to lean on your ORM to get the user object.
For Example:
<%= comment.user.age %>
This isn't any more efficient than what you've currently got, but it certainly makes the code look cleaner.
Another alternative: set a user variable in the view. You're not performing logic in your view at this point, you're simply storing some data to the heap for later use.

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

Rails Method not available in View

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.

Resources