Replacing foreign keys with data from table in ruby on rails - ruby-on-rails

I'm brand new to ROR and very new to programming.
I'm working on a DB and I want entering information to be easy and user friendly. In my index pages for my tables any foreign keys are shown as the id. I would like to learn how to have it display a different column value instead of the id. For example company_name instead of company_id.
From my very little experience I would guess that the .map method would be used. I'm not really sure how though. I've already messed around with it for a while with no success.
The lower half of one of my table's index.html looks like this:
<% #venture_rounds.each do |venture_round| %>
<tr>
<td><%= venture_round.raise_amount %></td>
<td><%= venture_round.company_id %></td>
<td><%= venture_round.investor_id %></td>
</tr>
What can I do to have it grab a value from the company and investor table and show it, instead of the id for those tables?
I hope this question makes sense.

Make sure your VentureRound model has company and investor as defined children
class VentureRound < ActiveRecord::Base
belongs_to :company, :investor
end
Read the information as such
venture_round.company.location # or whatever attributes you're seeking
venture_round.investor.name

Related

Rails has_and_belongs_to_many on 2 level association

This is related to this previously asked question
I have a has_and_belongs_to_many in place between Product and Supplier.
In my view I use:
<td><%= product.suppliers.map {|supplier| supplier.NAME }.join(', ') %></td>
To show list of suppliers comma separated on each row for each product in my table.
I now need to show the same list on invoices index view. Invoices table has a column PRODUCT. I have already set belongs_to :product on Invoice model.
I tried in my invoices index view:
<td><%= invoice.product.suppliers.map {|supplier| product.supplier.NAME }.join(', ') %></td>
but it returns
error undefined local variable or method `product'
Why isn't that working? How can I fix it? Thanks in advance.
you build wrong .map, try
invoice.product.suppliers.pluck(:NAME).join(', ')
BTW
it's bad practice use logic in view, you should move your logic to models, and in view use something like:
<%= invoice.suppliers_names %>
what should return # => 'Name_1, Name_2, etc'

Rails - get data of associated record

New to RoR, I am trying to get the data of an associated record to display in an index.
I got this to work:
<td><%= annotation.documenttype_id %></td>
Yet want the value of the name field of the documenttype.
I am using Rails 5. Experiencing Rails so far, it has got to be so simple...
From your code i can get that the association is:
annotation belongs_to document_type
So you can access the document_type from annotation like this:
annotation.document_type.name
<td><%= annotation.document_type.field_name %></td>

Native Ordering of a Model's Attributes

Does anyone know how Rails orders a model's attributes natively?
My issue is this:
I have a model I have been using for a long time called Device.
In devices/show.html.erb I show its attributes using something akin to:
<% #device.attributes.each do |k,v| %>
<tr>
<td><%= k %></td>
<td><%= v %></td>
</tr>
<% end %>
I have decided to add an attribute that is related to the 5th attribute in the model's attributes, but when I run the create_column migration for it, it appears at the end of this list (as it is the last attribute to be added).
I suspect Rails orders its attributes by column-creation time, as I have attempted to move the column to the correct place in my database, and declare the attribute sooner in my attr_accessible list, to no avail. Moving the column in schema.rb and rebuilding the database would probably work, but this is something I can't do. I could hack it into the right spot in the view, but I'm wondering if there is a better solution first.
Is there any way I can do this without enforcing ordering across the whole attribute list?
How about doing something like this:
1- Get the column_names (if you don't want to do it manually)
column_names = Device.column_names.inject([]) { |arr,e| arr.push(e) }
2- Modify the order that you want (i.e, a column name that you care about)
3- Evaluate each on #device
column_names.each_with_object({}) { |m, hash| hash[m] = #device.send(m) }
Rails migrations lets you specify where to add a column with the :after option:
add_column :your_table, :column_name, :data_type, after: :related_column
This could help. But, as this is a presentation concern, I'd order the attributes in a helper.

ID is being displayed instead of the name

I have a has_many_and_belongs_to_many relationship between items and builds.
Whatever I do, I just cannot get the name of the items to be displayed for How do I output name instead of ID? and the rest. It just displays the ID :/.
In the code provided below, the ID of the items are being displayed instead of the name of the item with that ID. How do I display the name of the item?
<% current_user.builds.each do |build| %>
<tr>
<td><%= build.hero.name%></td>
<td><%= build.user.email%></td>
<td><%= build.name %></td>
<td><%= build.starting_items %></td>
<td><%= build.early_items %></td>
<td><%= build.core_items %></td>
<td><%= build.situational_items %></td>
</tr>
<% end %>
Here is the github repo: https://github.com/imjp/DotA-Items
Basically, what I want to do is to be able to enter the id of an item in my form that later on gets displayed as the item name.
I'm not even sure if the name starting_items is the best for this, or if i should just use item_id. Because I'm planning on adding a lot of different inputs where users can enter items that need to be displayed as names.
Do you think the name starting_items is good for one of the fields, or should I use item_id?
Isn't there a way I can display the name of an item with ruby by doing something like Item.name.find(build.starting_items) or something? Since the starting_items value is an item_id?
When you call build.starting_items which I assume is a named scope or method call you are probably returning an array of the items themselves.
Following this it's likely that each of the items if being inspected or you are seeing the output of item.to_s.
In order to display the names of these items instead of the object id you probably wish to use something like:
<td><%= build.starting_items.map(&:name).join(', ') %></td>
This will call map on the array and pass out each of the names and then join them (you could also use .to_sentence in Rails).
Looking at the code you are retrieving a list of all items from the database and using them to fill a semantic form. If you take a look at the output of the items in the html it is creating a list of options with the item id as the value of the select.
When the form is saved it saves a string containing the single item ID in the database.
Firstly if you need to accept multiple items you could specify a number of has and belongs to many relationships such as:
has_and_belongs_to_many :starting_items, :join_table => "items", :foreign_key => "item_id"
You then need to make sure that the form actually passed the item id's along to ensure that all of the id's are saved correctly.
Ok from re-reading what you've written in response to the other answer, I now guess that the column that you're having trouble with is:
<td><%= build.starting_items %></td>
(let me know if that guess is wrong and tell me the right one).
Rails doesn't automatically know that you want to see the names of these items. It will give you exactly what you've asked for - and here you have asked for the set of actual items... not the names of the set of items. To turn these item-objects into a list of the names of the items, you must call the "name" method on each item. you can do that using the code the other answer gave:
<td><%= build.starting_items.map(&:name).join(', ') %></td>
UPDATE
ok, reading through your codebase.
The problem is that you aren't actually saving the starting_items as actual items. In the form where you create a "build", you have the starting_items field as a select-box... the select box stores a set of ids. and id is just an integer value.
nowhere in your code do you turn that set of integer id values into actual Item objects.
So... when you go to look at that list once more... it's still just a set of integers.
To see the names of the Items that have those ids, you will need to actually instantiate those Item objects... and then call the 'name' method on them eg:
<td><%= Item.find(build.starting_items).map(&:name).join(', ') %></td>
Note: you'll also need to add basic checks eg that build.starting_items is not empty, and that the ids are in fact valid ids...
I seem to have figured this on my own after buying a Ruby book.
First, I created a new helper method in application.rb called starting_item which goes as follows:
def starting_item(id="6")
#item = Item.find(id)
"#{#item.name}"
end
Why helper method? This way I can call this method from within my views.
Now, to display the name of the item in my index view, I do just call the method with the argument as follows: <%= starting_item(build.starting_items) %>

Data Transfer Objects VS Domain/ActiveRecord Entities in the View in RoR

I'm coming from a .NET background, where it is a practice to not bind domain/entity models directly to the view in not-so-basic CRUD-ish applications where the view does not directly project entity fields as-is.
I'm wondering what's the practice in RoR, where the default persistence mechanism is ActiveRecord. I would assert that presentation-related info should not be leaked to the entities, not sure though if this is how real RoR heads would do it.
If DTOs/model per view is the approach, how will you do it in Rails?
Your thoughts?
EDIT:
Some examples:
- A view shows a list of invoices, with the number of unique items in one column.
- A list of credit card accounts, where possibly fraudulent transactions were executed. For that, the UI needs to show this row in red.
For both scenarios, The lists don't show all of the fields of the entities, just a few to show in the list (like invoice #, transaction date, name of the account, the amount of the transaction)
For the invoice example, The invoice entity doesn't have a field "No. of line items" mapped on it. The database has not been denormalized for perf reasons and it will be computed during query time using aggregate functions.
For the credit card accounts example, surely the card transaction entity doesn't have a "Show-in-red" or "IsFraudulent" invariant. Yes it may be a business rule, but for this example, that is a presentation concern, so I would like to keep it out of my domain model.
In general I would answer that your AcitveRecord object can contain any fields and you show in views only what you want. There is scaffolding task in rails scripts but it is only to create some setup model, controller and view. When I work with Rails I don't use ./script/generate scaffold at all. Rather I'm generating only model and controller separatly. The view part I add manualy.
ActiveRecord only maps data from database to some nice objects. What you do with it in view is up to you.
According to separation between presentation and business rules I think that fallowing examples would make it clear to you how to handle it in Rails.
For your invoice example I would create a view this way:
<h1>Invoices</h1>
<table>
<tr>
<th>Invoice #</th>
<th>Date</th>
<th>Name</th>
<th>No. of line items</th>
etc
</tr>
<% #invoices.each do |invoice| %>
<tr>
<td><%= invoice.number %></td>
<td><%= invoice.date.to_s %></td>
<td><%= invoice.name %></td>
<td><%= invoice.line_items.count %></td>
etc.
</tr>
<% end %>
</table>
Or even put a row with invoice data into separate partial and render it in above view. I assume that in your model you have:
# Invoice model
has_many :line_items
Now lets take a look on credit card example. I would do it like this:
# In CreditCard model add method
def fraudulent?
#put here some logic that returns true or false
end
Then in your view when you render this credit card:
<div <%= #credit_card.fraudulent? ? 'class="show_in_red"' : '' %>
here you can show whatever you want
</div>
Or even create helper for it:
# credit card helper
def add_show_in_red(credit_card)
credit_card.fraudulent? ? 'class="show_in_red"' : ''
end
# in Rails 3 or earlier version with plugin that puts `h` method by default
# your helper should have additional safe_html! call
def add_show_in_red(credit_card)
(credit_card.fraudulent? ? 'class="show_in_red"' : '').safe_html!
end
and in view:
<div <%= add_show_in_red(#credit_card) %>>
here you can show whatever you want
</div>

Resources