Recursive association behaves differently in index/show/edit actions - ruby-on-rails

I'm trying to use recursive association in Ruby on Rails and I'm not getting the expected output :) I want to have match with two players (winner and loser) and I thought about recursive association.
I used scaffolds to get the basics done:
rails g scaffold Player firstname:string lastname:string
rails g scaffold Match date:date result:integer winner:references loser:references
And then modified Match model:
class Match < ActiveRecord::Base
has_one :winner, class_name: 'Player', foreign_key: 'id'
has_one :loser, class_name: 'Player', foreign_key: 'id'
end
However if I add manually some players and go to match#index both winner and loser are the same (even if I set them to be different).
Part of view is like this:
<tbody>
<% #matches.each do |match| %>
<tr>
<td><%= match.date %></td>
<td><%= match.result %></td>
<td><%= match.winner %></td>
<td><%= match.loser %></td>
<td><%= link_to 'Show', match %></td>
<td><%= link_to 'Edit', edit_match_path(match) %></td>
<td><%= link_to 'Destroy', match, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
When I edit Match, then I clearly see that there are different IDs of players, but in show or index both players are the same.
What am I doing wrong?
Thank you :)

You are using an "has_one" association, which is actually setting the database link on the other class/table (here, Player). This is not what you want: you want to have two foreign keys (winner_id and loser_id) on the Match table.
This can be done using the much more common "belongs_to" association:
class Match < ActiveRecord::Base
belongs_to :winner, class_name: "Player"
belongs_to :loser, class_name: "Player"
end

Related

display "name" insted of "_id" in index view

I've got "demand", "shift" and "parent" (this is going to be a baby sitter thingy).
Now the models look like this:
class Demand < ActiveRecord::Base
belongs_to :parent
belongs_to :shift
end
&
class Parent < ActiveRecord::Base
has_many :demands, :dependent => :destroy
has_many :shifts, :through => :demands
accepts_nested_attributes_for :demands, allow_destroy: true
# Returns fullname of parent
def fullname
"#{firstname} #{name}"
end
end
&
class Shift < ActiveRecord::Base
has_many :supps, :dependent => :destroy
has_many :nanns, :through => :supps
has_many :demands, :dependent => :destroy
has_many :parents, :through => :demands
end
If I now want to display a shift's description (a param of the shift table) instead of its _id, I get the following error:
undefined method `description' for nil:NilClass
Here is some code from the corresponding demands index view:
<td><%= demand.parent.name %></td>
<td><%= demand.demand %></td>
<td><%= demand.shift.description %></td> <----THIS LINE PRODUCES THE ERROR
<td><%= link_to 'Show', demand %></td>
<td><%= link_to 'Edit', edit_demand_path(demand) %></td>
<td><%= link_to 'Destroy', demand, method: :delete, data: { confirm: 'Are you sure?' } %></td>
I think that I gave the models the correct has_many and belongs_to associations so I don't really find the mistake here. Thanks in advance for any help!
You have a demand that has no associated shift. If you want to identify which one in your table, replace...
<td><%= demand.shift.description %></td>
with
<td><%= demand.shift ? demand.shift.description : 'missing shift!' %></td>
The lines with missing shifts will now tell you that shift is missing.

Ruby on Rails Accessing attributes of record found using .last

In my asset index view, when it loops through each Asset, I want to show the most recent scene name. I am using .last to pull out the most recent record. When I .inspect what is returned, I can see the values. The problem is, when I try to access one of the attributes of what is returned I get an undefined method.
So for instance if I do this:
<%= (asset.scene_assignments.where(asset_id: asset).order("created_at").last).scene_id %>
I get:
NoMethodError in Assets#index undefined method `scene_id' for SceneAssignment:0x4bc2c28
But if I call #inspect instead of #name, I can see what is contained inside. So if I do this:
<%= (asset.scene_assignments.where(asset_id: asset).order("created_at").last).inspect %>
It prints this:
SceneAssignment id: 4, scene_id: 3, asset_id: 1, arrival_time: nil, created_at: "2014-10-16 01:43:50", updated_at: "2014-10-16 01:43:50", location_id: 1, asset_role_id: 1
Why can't I access one of the attributes from what is returned?
In my asset index view, I have this:
<% #assets.each do |asset| %>
<tr>
<td><%= asset.name %></td>
<td>
<%= (asset.scene_assignments.where(asset_id: asset).order("created_at").last).inspect %>
</td>
<td><%= link_to 'Show', asset %></td>
<td><%= link_to 'Edit', edit_asset_path(asset) %></td>
<td><%= link_to 'Destroy', asset, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
My relationship between an asset and scene is set up like this:
class SceneAssignment < ActiveRecord::Base
belongs_to :scene
belongs_to :asset
belongs_to :location
belongs_to :asset_role
belongs_to :incident
accepts_nested_attributes_for :asset
end
class Scene < ActiveRecord::Base
has_many :scene_assignments
has_many :assets, :through => :scene_assignments
belongs_to :incident
belongs_to :scene_type
accepts_nested_attributes_for :scene_assignments, :allow_destroy => true
end
class Asset < ActiveRecord::Base
has_many :scene_assignments
has_many :scenes, :through => :scene_assignments
end
I believe it is .scene_id, not .last that is causing your error. The result of `.where(…) is a collection of records, not a single record.
To fix that, you could say asset.scene_assignments.where(asset_id: asset).order("created_at").last).first.scene_id though that makes some fairly messy code just a little bit worse. :)

Getting uncategorized objects in has_many :through

Good day all,
Pardon me for my noob-ness in rails.
So here's my question.
So I've a category model and a itinerary model defined below
class Category < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
has_many :itineraries, :through => :categorizations
end
class Itinerary < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
end
So in my view, I am looping through categories to display itineraries in groups.
<% #categories.each do |category| %>
<table>
<thead>
<tr>
<th colspan="4"><%= category.name %></th>
</tr>
</thead>
<tbody>
<% category.itineraries.each do |itinerary| %>
<tr>
<td><%= itinerary.name %></td>
<td><%= link_to 'Show', itinerary %></td>
<td><%= link_to 'Edit', edit_itinerary_path(itinerary) %></td>
<td><%= link_to 'Destroy', itinerary, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
So I am wondering, how do we display itineraries that are not categorised yet?
I've searched around and found out that using scopes in the model will be the way to go.
scope :without_categories, -> { includes(:categorizations).where( :categorizations => { :itinerary_id => nil } )}
I find it not very "DRY" due to the fact that I've to write another table to iterate through itinerary.without_categories again.
Is there a way where we're able to code it in such a way where categories.all shows everything with uncategorized items in it?
Thank you.
Update #1
Decided to use this in my controller, which builds a new "Uncategorized" category on index action and it'll add to the array.
def index
uncategorized = Category.new
uncategorized.name = "Uncategorized";
uncategorized.itineraries = Itinerary.without_categories
#categories = Category.all << uncategorized
end
I know that in rails, controllers should be as skinny as possible.
But I can't think of a better way.
Anyone with a better answer, please feel free to share. Thanks! :)
You just have to find the itineraries that dont have a reference inside the Categorizations table. You can do a nested query for this.
SELECT * FROM itineraries where id NOT IN ( SELECT itinerary_id FROM categorizations')
just do a method in your Itinerary model like this:
def self.uncategorized
Itinerary.find_by_sql('SELECT * FROM itineraries where id NOT IN ( SELECT itinerary_id FROM categorizations)')
end

Easy Rails question re: displaying data in secondary table

Easy for anyone but this newbie, I'm sure, but I can't find the answer anywhere. I have a User model and a Role model, with role_id in the users table; I want to show the actual role (Admin, Visitor, etc) which resides in the roles table, on my users index page.
The pertinent section of the index.html.erb:
<% #users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td><%= user.role_id %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
So what do I put in place of user.role.id?
role.rb:
class Role < ActiveRecord::Base
end
user.rb:
class User < ActiveRecord::Base
has_one :role
end
I'm using Rails 3, fwiw.
TIA
Your models are indeed configured incorrectly, but from what I can understand of the question you want the following.
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
end
And then you can do the following:
<%= user.role.name_field %>
This will allow multiple Users to all have the same role. Instead of enforcing a one to one relationship. No schema change is needed.

Accessing Attributes in a Many-to-Many

I have a rails app and I'd like to be able to do something like
task.labels.first.label_name to get the label name of a task. However, I get an undefined method label_name. I did a t = Task.first; t.labels.first.label_name in the console, and that worked so I'm not sure what's going on. Here's the models then the locations of the error:
class Categorization < ActiveRecord::Base
belongs_to :label
belongs_to :task
end
class Label < ActiveRecord::Base
attr_accessible :label_name
has_many :categorizations
has_many :tasks, :through => :categorizations
end
class Task < ActiveRecord::Base
attr_accessible :task
has_many :categorizations
has_many :labels, :through => :categorizations
end
The error is in the index
<% for task in #tasks %>
<tr>
<td><%= task.task %></td>
<td><%= task.labels.first.label_name %></td>
<td><%= link_to "Show", task %></td>
<td><%= link_to "Edit", edit_task_path(task) %></td>
<td><%= link_to "Destroy", task, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
My guess would be that one of the tasks in #tasks does not have any labels so when you call task.labels.first it returns nil and then you try to call label_name for nil which of course does not work.
The easiest solution would be to do a check like this:
<td><%= task.labels.first.label_name unless task.labels.first.nil? %></td>
Now that does not look so good in the view so you might want to place that check in your Task model instead, perhaps like this:
class Task < ActiveRecord::Base
attr_accessible :task
has_many :categorizations
has_many :labels, :through => :categorizations
def label_name
self.labels.first.label_name unless self.labels.first.nil?
end
end
And in the view:
<td><%= task.label_name %></td>
And another thing, just in case you would like to view all the associated labels, you could do something like this:
task.labels.map(&:label_name).join(", ")

Resources