Rails Creation through association & Collection vs. Array - ruby-on-rails

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.

Related

Ruby on Rails, getting attributes from multiple model instances

In my Ruby on Rails project, I have a Country model class and a Proposal model class. A country has many proposals and a proposal belongs to a specific country. Each proposal consists of 30 questions. I want to display each question for every proposal for that specific country.
My initial idea for syntax was:
<% #country.proposal.each do |proposal| %>
<% end %>
However, this resulted in an error message
undefined method `proposal' for #Country:0x007f67b51cf178
Any ideas?
According to yous associations, just replace #country.proposal.each with
#country.proposals.each. has_many association define method with a name in plural form.

Access Attribute of Rails Test Fixture in Associated FixtureSet

Background
My application defines a one-to-many relationship between Employee and Company models. I've assigned the Employee fixture a company using the label of the Company fixture (37_signals). However, I also need to assign the company_uuid, which is generated by SecureRandom.uuid.
Example
app/models/
employee.rb
class Employee
belongs_to :company
end
company.rb
class Company
has_many :employees
end
test/fixtures/
employees.yml
employee:
name: dhh
company: 37_signals
company_uuid: <%= "Access the 37_signals company fixture's uuid here!" %>
companies.yml
37_signals:
name: $LABEL
company_uuid: <%= SecureRandom.uuid %>
Question
How can I access the attribute of a Fixture in another FixtureSet?
Attempted
I've attempted to use the following lines as solutions:
company_uuid: <%= ActiveRecord::FixtureSet.identify(:37_signals).company_uuid %>
The above finds the company's primary key id value, then calls the company_uuid method, which is undefined for the integer. This is invalid.
company_uuid: <%= companies(:37_signals).company_uuid %>
The above finds reports undefined method 'companies' for main:Object
Is there a conventional way to solve this problem?
Hmmm, the first option should be to DRY out the data, so it only exists in one place. You could do this with a delegate so that employee.company_uuid will always be answered by employee.company.company_uuid.
If you really need it in the Employee model, the next best choice would be to use a callback (like before_validate or after_save depending on your use-case), that copies the value from the Company to the Employee object. You want to eliminate chances for the data value to diverge from what it's true source should be.
Finally, you could extract all the UUIDs into a hash accessible to both fixtures at the time of creation, and set both values like:
company_uuid: <%= UUIDs['37_signals'] %>
...or similar
This is the best solution I've been able to devise:
company_uuid: <%= Company.find(ActiveRecord::FixtureSet.identify(:publish_and_export_album)).company_uuid %>
However, this solution does not seem conventional. Also, I believe that this succeeds with some luck since fixtures are loaded alphabetically. If Company was named Organization, loading after Employee, then I think this would not work as intended. Actually, through trial and error I determined that this method works in the opposite direction, so the alpha-order has no detrimental effect.
This isn't an issue if you use the fixture_builder gem. It allows you to build a graph of model objects and give them fixture names, and saves them to .yml fixture files for you.

Eager Load Nested Associations with Conditional

I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).

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.

Rails Foreign Key Issue

Sorry if this question seems simple, I am very very new to Rails (just started learning a few days ago), but after consulting Google and "Agile Web Development with Rails" I can't find the answer.
I have an issue with Rails 2.3.8 creating a foreign key on two models. My tables look like this:
cars manufacturer
---- ------------
car_make name
car_model country
car_class logo_url
image_url (and default 'id' created by Rails)
manufacturer_id
(and default 'id' created by Rails)
My 'car_make' and 'name' fields are essentially the same; every Car I create, I want to be able to associate it with an existing Manufacturer. This is the column I am trying to create FK on.
My car.rb has 'belongs_to :manufacturer', and my manufacturer.rb has 'has_many :cars' to establish a one manufacturer to many cars relationship. However, when I create a new car (via scaffolding) the manufacturer_id field is blank.
I went to my cars_controller, found the 'create' method that is being used, and tried to add the second line below:
#car = Car.new(params[:car])
#car.manufacturer_id = car.manufacturer.id # <===
This produces a 'NameError in CarsController#create' error, and I see:
undefined local variable or method 'car' for #<CarsController:0x1034642f0>
Rails doesn't seem to like the line I've added. What am I missing to make this work?
Well, you need to have a manufacturer available before you can attach it to the car.
#car = Car.new( params[:car] )
m = Manufacturer.first # => as you can see you must already have one made
#car.manufacturer = m
#car.save
The reason car is undefined is because, well, you haven't defined it. Which car's manufacturer did you want to assign to #car?
So basically you need to make a manufacturer before you make a car. If the form you're filling out has the data for the manufacturer then make sure to put that under a different key in params, like, say, params[:manufacturer] and do a similar thing as you're doing with the car. Maybe like:
#car = Car.new( params[:car] )
#manufacturer = Manufacturer.find_or_create_by_name_and_country( params[:manufacturer][:name], params[:manufacturer][:country] )
#car.manufacturer = #manufacturer
#car.save
In your view, you want to generate a drop-down list for manufacturers (I would assume), so you should do something like this in the form:
<%= collection_select(:car, :manufacturer_id, Manufacturer.all, :id, :name) %>
Then your create action shouldn't need to explicitly set a manufacturer_id because it should have received that from the form.

Resources