I've built a small web app in which main view shows a table and administrator has an option to remove a row by deleting it from a database using:
*.destroy
However I want to keep all the entries in the database but still would like the option for the user to be able to remove the rows from the table and not sure how to go about this. I was thinking about using two different database tables but wanted to check if there is maybe a simpler way?
Here is my main view:
<h1>Student List</h1>
<table class ="Tables">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date Submit</th>
<th>Floor Preference</th>
<th></th>
</tr>
<% #students.each do |student|%>
<tr>
<td><%= student.f_name %></td>
<td><%= student.l_name %></td>
<td><%= student.created_at %></td>
<td><%= student.floor_pref %></td>
<td><%= button_to 'Remove', student, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Student', new_student_path %>
you can easily do this by creating an extra column in same table with boolean value.
So extra column name can be isActive.
Removed row's isActive would be 0 and rest have 1.
And when fetching data just put extra check where isActive = 1
rails g migration AddActiveToStudents active:boolean
Then in view you can alter the value when they click the "Delete" button
Then put a check after the .each do |student|
<% if student.active %>
..........display..............
<% end %>
This solution is a little more labor intensive, but works for me. My steps are similar but I'll provide more detail because I'm new to programming so I'm going to assume there are others who are new as well:
Assumptions:
- You're working from a single model, views and controller (e.g. Model is Foo)
- You're able to delete a record from the db by clicking on a button that initiates a controller action ("Destroy")
- If you've got associations, destroying a record from the db is problematic to your app.
Created a migration for new column named is_active, w/datatype: boolean. Verify that rails created the migration correctly. If not, manually tweak the migration file. Make sure you bundle exec rake db:migrate after ANY change to the db. Restart your server.
Edit the destroy action in the controller by setting the is_active variable to false. Boolean evaluates to 'true' or 'false'. For us 'false' will represent a "deleted" row (although it won't be deleted in the database, just hidden from the view. e.g. #foo.is_active = false. Be sure to use the "=" operator, which assigns the value. In the view you'll use a different operator (==). Make sure you do a save after any editing. e.g. #foo.save. Also, comment out any destroy code. e.g. # #foo.destroy
2a. I also set the is_active value to 'true' in my create action. This may not be necessary but it works for me so far. If you made this adjustment, remember to save. e.g #foo.is_active = true, #foo.save
In the view where your results will show, add a conditional statement. I'm looping through my table and painting results. My if statement is after my do loop. If it evaluates to 'false' I'll show a line of text. ELSE, if it evaluates to 'true' I'll show my data. (Generally, on subsequent lines I have: Do Loop, 'False' iF Statement, Else, 'True' Data , End tag).
So, in the "if" statement I'm telling rails that when you're looping thru, if you see a row where the foo.is_active is 'false', show this text. Otherwise (else) show the data between 'else' and 'end'. Notice how the operator in the view is different than the controller. In the view we're checking for equality (==), not assigning a value to (=).
Make sure the variables that you're calling match what you've set in your controller's destroy action. They must be consistent or you'll get error messages. This gave me problems for a few hours before I figured it out. So if you're setting #foo in your controller, then you can only call #foo in your view.
Finally, the view I'm working with is my index but I also but a conditional statement in my show view so my users can toggle is_admin on or off. Using a simple_form_for form, I added the is_admin variable as boolean which shows as a checkbox on my show form. When checked is_admin is 'true' and the row will show. When unchecked is_admin is 'false' and therefore hidden.
Related
I get that one should not ping the database in the view... but wondering about the right solution. In one of my views, I need to pull info on an #order, it's child items, and also Amount, another model, based on each child item. Something like this:
<% #order.items.each do |item| %>
<td><%= item.name %></td>
<td><%= Refund.where(item_id:item.id).first.amount %></td>
<td><%= Amount.where(item_id: item.id).first.amount %></td>
<% end %>
For the sake of avoiding the db hits in the view, the only solution I've thought of is to create a huge hash of all the relevant data in the controller, which is then accessed from the view. So it would be something like this:
# controller (writing quickly, code may not be totally right, hopefully you get gist
data = Hash.new
data["items"] = []
#order.items.each do |item|
item_hash = {
"name" => item.name,
"amount" => Amount.where(item_id: item.id).first.amount,
"refund" => Refund.where(item_id:item.id).first.amount
}
data["items"] << item_hash
end
# view code
<% data["items"].each do |item| %>
<td><%= item["name"] %></td>
<td><%= item["refund"] %></td>
<td><%= item["amount"] %></td>
<% end %>
And I know SO hates this type of question... but I really need to know... is that the best solution? Or are there are best practices? The reason I ask is because it seems very clean in the view, but very bulky in the controller, and also it gets quite unwieldy when you have a much more complex set of nested tables, which is what I actually have (i.e., the data hash would be quite funky to put together)
First of I would use associations between item and the 2 other classes, so that you can do
item.refund
item.amount
Instead of Refund.where(...). You could further define methods such as
def refund_amount
refund.amount
end
And similarly for the other one (and hopefully come up with a better name than amount_amount.
This keeps both your view and controller clean but it won't be any faster. So far all of the approaches involve running 2 database queries per item which is the real issue as far as I'm concerned - whether those excess queries happen in the view or the controller is of lesser concern.
However you can avoid this with Active Record's include mechanism:
Item.include(:amount,:refund).where("your conditions here")
Will load the named associations in bulk rather than loaded them one at a time as each item is accessed.
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.
I'm trying to set up something like a shopping cart, and what I have is a form to choose how many of each size shirt someone may want to buy. I plan on having XXS, XS, S, M, L, XL, and XXL, and they choose a quantity of each and add it to the cart. The simplest way I could think of doing this was to have a t.integer "size_??" for each size in a database for 'tshirt', and after the user clicks add to cart, that digit is then stored in the session until their payment is approved, in which case it is then saved to the database. Then someone could view the database later to see what orders were placed. (This would be for making custom shirts, so there would be no 'examples' saved on the database that need to be viewed or listed).
My first question is if that is correct. I thought of using a "Shirts has_many Sizes" approach, but I'm not the most familiar with it, and if all I am trying to achieve is to obtain a quantity, I feel like that might be a little overkill.
My second question is this: if I have a form to select the quantity of each, how do I assign the values to the session variable. My first thought was an approach like this:
new_order.html.erb:
<%= form_for(:shirt, :url => {:action => 'save_to_session'}) do |f| %>
<table summary="New shirt order form">
<tr>
<th>Size S:</th>
</tr>
<tr>
<td><%= f.select(:size_s, 0..99) %> </td>
</table>
<%= submit_tag("Place order") %>
<% end %>
and on the controller side
def new_order
#temp_order = Shirt.new
#temp_order.size_s = 0
end
def save_to_session
session[:shirt_size_s] = #temp_order.size_s
redirect_to(:action => 'show_session')
end
def show_session
end
where show_session just spits out the session[:shirt_size_s]
The error Im getting with that is
undefined method `size_22' for nil:NilClass
Perhaps this is taboo, as it seems to be a very simple approach, but this will not change the session value. If I add the following line to save_to_session it works, so I'm thinking perhaps save_to_session doesn't have access to #temp_order, but I'm still in the learning phase and don't exactly know what is going wrong here.
session[:shirt_size_s] = 12
Your #temp is an instance variable normally used for populating the page and not used for retrieving data. You want to user the params[:field1] for that where field1 would be the name of your select.
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) %>
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>