Alter all attributes printed in views on ruby on rails - ruby-on-rails

My rails application only reads data on it's database. There's no save.
Mostly all my views are a form with a few fields to filter my search and a table printing every column's value. Something like that:
<table>
<tr>
<th> Col1 </th>
<th> Col2 </th>
<th> Col3 </th>
...
</tr>
<% Model.all.each do |model| %>
<tr>
<td> <%= model.col1 %> </td>
<td> <%= model.col2 %> </td>
<td> <%= model.col3 %> </td>
...
</tr>
<% end %>
There's over 50 models each with 10-20 fields, almost 100 tables. It's all very straightforward.
Now my client asked me that when any cell is empty (the data is null or '') he wants it to print '-' instead of the blank cell on the table
At first I thought about something like this on my application_helper.rb:
def avoid_empty(object)
if object == nil or object == ""
return "-"
else
return object
end
end
and everywhere on my views I would just change
<%= model.col1 %>
to
<%= avoid_empty(model.col1) %>
but I actually have 2659 lines like that. I'm not sure it's the healthier approach.
Is there any way I can change it everytime I try to print any value from those models that I could go through that method or something similar first, without having to change every single one of my 2659 lines?

You can use the power of ruby's meta-programming and ActiveSupport::Concern.
Create a model extension module to overwrite the getter of all columns dynamically. Below code can do that:
module ModelExtension
extend ActiveSupport::Concern
# To exclude some columns
EXCLUDE_COLUMNS = ['id', 'created_at', 'updated_at']
included do
(self.column_names - EXCLUDE_COLUMNS).each do |col|
define_method col do
self[col].blank? ? '-' : self[col]
end
end
end
end
Include this module in each model to get '-' instead of blank or nil. To include model, you can do:
class MyModel
include ModelExtension
end

You can monkeypatch NilClass.
class NilClass
def to_s
'-'
end
end
Everytime you invoke, say, nil.to_s, it will print '-'. If you type puts nil in the console, will print -. Try the following example:
class NilClass
def to_s
'-'
end
end
foo = nil
foo.to_s #=> '-'
puts foo #=> -
In your case, all occurrences of empty (nil) attributes will output - in the view when trying to print (and, be warned, in other parts of your application too).
PS.: #to_s returns a string representing the instance object.

A solution maybe
The Null Object Pattern
Null Object is an object with some default behavior that implements the same interface as an other object that might be used in a given case. Ok, cool, but how can we apply it to the example below? Let's start with the user method:
def user
#user ||= User.find_by(email: email) || NullUser.new
end
And now we can implement NullUser:
class NullUser
def name
"Anonymous user"
end
end

Related

Rails Object attribute value is getting modified while accessing it in Controller

I have a News Model with attributes like email, title, slug, ..etc.I wanted to access the slug attribute of an object inside a method in my visitors_controller.
In the rails console, the value of slug attribute is
2.3.3 :002 > #news = News.order(created_at: :asc).last(1)
The result is:
slug: "north-korea-threatens-to-shoot-down-u-s-warplanes"
I also checked the slug value in my database
select slug from news where id = xxx;
The result is :
north-korea-threatens-to-shoot-down-u-s-warplanes
The value of slug is appended with " . . . " while accessing it inside of a method in a controller .
class VisitorsController < ApplicationController
def getSlug
#news = News.order(created_at: :asc).last(1)
#news.each do |news|
slug = news.slug
# raise slug.to_yaml
end
end
When I raised it, the slug value is
--- north-korea-threatens-to-shoot-down-u-s-warplanes ...
getSlug.html.erb
<% #news.each do |bubble_gum| %>
<tr>
<td><%= bubble_gum.title %></td>
<td><%= bubble_gum.slug %></td>
</tr>
<% end %>
in the view, the slug value is rendering correct, without (. . .) appended to the slug.
How can I get the value of the slug attribute without the appended content( . . .) inside of a method in a controller.
Any Help is Highly Appreciated.Thanks in advance!!
This has got nothing to do with the ActiveRecord query, or being inside a controller.
The ... you are seeing is simply caused by converting the string to YAML:
"north-korea-threatens-to-shoot-down-u-s-warplanes".to_yaml
# => "--- north-korea-threatens-to-shoot-down-u-s-warplanes\n...\n"
From wikipedia:
Three periods (...) optionally end a document within a stream.
In the controller, you are converting the string to YAML (raise slug.to_yaml), and in the view you are just displaying the string directly (<%= bubble_gum.slug %>). That's why you are seeing a difference.

I want to make a helper and template to dynamically generate a view for a collection of active record objects

This may sound strange, but none the less I want to learn how to do it and I need some help getting there. I'm not sure how to approach this. I'm hoping to get some dev love on this.... Let me explain by giving an example. (Btw thank you---you are awesome!)
Instead of this in my view:
<table>
#users.map do |user|
...
</table>
I want to extract it away into a helper that I can reuse for other collections.
So I want to say instead:
#users.to_table({
template: "simple_template",
header: ["Full Name","Email"],
column: ["name", "email"]
})
So in my application_helper I have something like this: (pseudo-ish code)
class ActiveRecord::Relation
def to_table *args
load args.template
self.map do |j|
args.header do |header|
j.header
end
args.column do |column|
j.column
end
end
end
end
I have no idea how to wire this up. (helper or table template) Definitely an order of magnitude above my current skill level. Need some serious direction.. I'm asking this because I feel like I hit a learning plateau and need help busting through to something more challenging (hence this question)... Hope it's clear, if not ask for clarification. Thanks for reading... Thanks for helping! =)
Not guaranteeing this will work it is just to show the syntax issues:
class ActiveRecord::Relation
def to_table(options={})
load options[:template]
self.map do |j|
Hash[
args[:headers].zip(args[:columns].map{ |column| j.send(column) }
]
end
end
end
Not Sure about the load part I think this should be handled outside of the relation as it is a view issue and has nothing to do with the ActiveRecord::Relation but this method will return an Array of Hashes like
[{"Full Name" => "USER 1 Name", "Email" => "USER1#email.com},{"Full Name" => "USER 2 Name", "Email" => "USER2#email.com"}]
In your current method args which is an array now based on the * will not respond to things like template or column. Like I said I have never really tried to implement anything in this way but the syntax change might get you headed in the right direction. Also handling should be put in place for when template is not passed or headers.count != columns.count.
Best bet is probably something like this
<%= render "template", obj: #user.to_table(headers: ["Full Name","Email"],columns: ["name", "email"]) %>
in _template.rb
<table>
<thead>
<tr>
<% obj.first.keys.each do |header|
<th><%= header %></th>
<% end %>
</tr>
</thead>
<tbody>
<% obj.each do |row|
<tr>
<% row.values.each do |cell| %>
<td><%= cell %></td>
<% end %>
</tr>
<% end %>
</tbody>
Although if I had more time to think there are probably far simpler implementations of this maybe something like
<%= render 'template', locals:{collection: #users, headers: ["Full Name","Email"], columns: ["name", "Email"]} %>
UPDATE
I think making a view helper might be better in this instance like this
def make_table(collection,options={})
content_tag(:table,options[:table_options]) do
content_tag(:thead) do
content_tag(:tr) do
options[:headers].map do |header,header_options|
content_tag(:th,header,header_options,false)
end.join.html_safe
end
end
content_tag(:tbody,options[:body_options]) do
collection.map do |obj|
content_tag(:tr,options[:row_options]) do
options[:columns].map do |column,cell_options|
content_tag(:td,obj.public_send(column),cell_options,false)
end.join.html_safe
end
end.join.html_safe
end
end
end
call as
<%= make_table(#users,columns:{name:{class: "name"},email:{}},headers:{"Full Name"=>{class:"name_header"},"Email"=>{}}) %>
or without formatting
<%= make_table(#users,columns:[:name,:email],headers:["Full Name","Email"]) %>
This method requires an object collection and will accept the following through the options Hash
:table_options as a Hash to pass to the content tag for formatting the table
:headers as an Array or Hash (for formatting header rows)
:body_options as a Hash to pass to the content tag for formatting the table body
:row_options as a Hash to pass to the content tag for formatting the rows
:columns as an Array or Hash (for formatting the individual cells)
You can place this method in helpers/application_helper.rb and you will have access to it throughout the application. Although I have not fully vetted this method and it is currently more conceptual than anything else.

How to overide user selected attribute based on another attribute's value

I have a form where I want a checkbox selection to override the user's selected value for another attribute.
The form:
<%= form_for(#foo) do |f| %>
<tr>
<th>FooType</th>
<td><%= f.select :foo_type_id, options_from_collection_for_select(FooType.order(:name).all, "id", "name", #foo.cabinet_type_id) %></td>
</tr>
<tr>
<th>Legacy Foo</th>
<td><%= f.check_box :is_legacy %> </td>
</tr>
Where do I put code to overwrite the user's selection for :foo_type based on if :is_legacy is checked? I tried adding an update_attribute command after saving the new Foo, but it doesn't seem to work:
if #foo.save_standard and ! row.nil?
#set foo type to 38u when it is a legacy foo
if params[:is_legacy] == true
#foo.update_attribute(foo_type_id, 2)
end
I think there are a couple of mistakes in the following code:
if #foo.save_standard and ! row.nil?
#set foo type to 38u when it is a legacy foo
if params[:is_legacy] == true
#foo.update_attribute(foo_type_id, 2)
end
end
Try:
if #foo.save_standard && row
if params[:foo][:is_legacy]
#foo.update_attribute(:foo_type_id, 2) # careful, this method do not fire callbacks
end
end
Assuming your instance object is a Foo class (which is probably not). If not, replace
params[:foo]
by whatever your instance class is.

How to display all instances of a model in Rails?

In my web application the user can select certain instances of an entity. For instance on the class BubbleGum, now the user can select certain instances of BubbleGum by adressing their identifier:
gums/details?show=3532667
Now, in addition I also want to make it possible to display all BubbleGums. For this I have introduced the convention of using * to identify all
gums/details?show=*
This works nice so far, but often I have to add a bit code to process the * selection. Is there a nice way to represent an all-instances object in Ruby and/or Rails?
I have thought of using simply a dedicated symbol, constants, or an actual object of the class BubbleGum that represents all the other bubble gums.
To display all the entities in a rails application generally we use a index page.
bubble_gums_controller.rb
def index
#bubble_gums = BubbleGum.all
end
views/bubble_gums/index.html.erb
<% #bubble_gums.each do |bubble_gum| %>
<tr>
<td><%= bubble_gum.name %></td>
<td><%= bubble_gum.price %></td>
</tr>
<% end %>
Refer this for further details.
http://guides.rubyonrails.org/getting_started.html#listing-all-posts
I think you want to use the query string param show.
So, you can try in your gums controller:
def details
if params[:show] == "*"
#bubble_gums = BubbleGum.all
# ...
elsif params[:show]
#bubble_gum = BubbleGum.find(params[:show])
# ...
else
render :status => 404
end
end

Ruby on Rails: Sorting collection of objects

I have a method that will provide an array of model object. Some of those model's attributes are easy to sort by using some help from SQL. I mean, using .find(:condition => {})
The problem is some of the other attributes isn't. It has to be calculated, modified, or did something else before showing. Then, I found the way to sort it using collection sorting. Which is, collection.sort{|a,b| a.something_to_sort <=> b.something_to_sort}
OK! That's works. But the problem is, is it possible to make that something_to_sort part to become a dynamic variable? For example, I want to take a parameter from the UI and assign it to that something_to_sort like following,
HTML
<select name="sort">
<option value="name">Name</option>
<option value="age">Age</option>
<option value="activity.category">Activity</option>
<option value="tax.calculate_personal_income_tax">Income Tax</option>
<option value="tax.calculate_withholding_tax">Withholding Tax</option>
<option value="doctor.name">Doctor's Name</option>
</select>
Rails
params[:sort] ||= "Age"
#people = Person.find(:all)
#people.sort{|a,b| a.params[:sort] <=> b.params[:sort]} #Note that this is an incorrect syntax
Is there any way to do this by not having to write a sort block for each of sorting option?
Additional #1
I've just tried this and it was working for some sorting option
#people.sort{|a,b| a.send(params[:sort]) <=> b.send(params[:sort])}
This works for name and age as they are people's attribute (and it works for peopls's method too). But for the association's such as tax.calculate_personal_income_tax, it's not.
So, my colleague told me to create new people's method, calculate_personal_income_tax and then change the option value from tax.calculate_personal_income_tax to calculate_personal_income_tax so that the send method will be able to find this method ...
def calculate_personal_income_tax
tax.calculate_personal_income_tax
end
But, this is not really what I want. Because I believe that there must be a way to access this association method without having to define a new model method. If I choose to do this and one day people model become larger, more attribute, more information to sort and display. Then, there will be a lot of method like this.
The other reason is, if I have to define a new method, that method should have to do some logic, calculation or else, not just retrieve a data from other model.
Additional #2
Finally, I've just found the way to access other model attribute by adding args to the find method, :join and :select.
#people = Person.find(:all, :select => "persons.*, tax.tax_rate AS tax_rate", :joins => :tax, :condition => {some conditions})
The result of that will be a table of person joining with tax. Then, I use the send(params[:sort]) thing to help me sorting the attribute that I want.
#people.sort {|a, b| a.send(params[:sort]) <=> b.send(params[:sort])}
params[:sort] ||= "age"
method_chain = params[:sort].split(".")
#people = Person.find(:all)
#sorted_people =
#people.sort_by do |person|
method_chain.inject(person) do |memo,method|
memo.send(method)
end
end
This assumes that the values passed in params[:sort] are always valid method names which can be chained together in the order specified. You'd want to keep in mind that anything can be passed in the params hash, so this would be very unsafe if exposed to untrusted users (e.g. params[:sort] = "destroy". Oops.)
Also, if you can do this in SQL instead you'll get better performance.
In fact, the more I look at this, the more it seems like a bad idea.
I have a loot system that is doing this on a calculated value, that is based on a reference table which can change. As the value is calculated, it could come from anywhere.
My players/index.html.rb looks something like.
<h1>Players</h1>
<table>
<tr>
<th>Name</th>
<%= generate_headings(params) %>
</tr>
<% players.each do |player| %>
<tr>
<td><%= player.name %></td>
<% LootType.all.each do |loot_type| %>
<td><%= player.loot_rate(loot_type.name) %></td>
<% end %>
<tr>
<% end %>
</table>
The loot_rate function is calculating on two related tables (player.raids and player.items) as "player raids / loot of type + 1".
The generate_headings function is in players_helper.rb, and it looks like:
def generate_headings(params = {})
sort = params[:sort]
headings = ""
LootType.all.each do |loot_type|
heading_text = "#{loot_type.name} Rate"
if (sort == loot_type.name)
column_heading = "<th>#{heading_text}</th>"
else
heading_url = "/players?sort=#{loot_type.name}"
column_heading = "<th>#{link_to heading_text, heading_url}</th>"
end
headings += column_heading
end
headings.html_safe
end
In my index action of players_controller.rb, I have:
def index
sort = params[:sort]
if !sort
#players.sort! { |a,b| a.name <=> b.name }
else
#players.sort! { |a,b| b.loot_rate(sort) <=> a.loot_rate(sort) } # Reverse sort (for my purposes)
end
respond_to do |format|
format.html
format.json {render :json => #players }
end
end
This means that when I add or remove "loot types" in my system, and the player list automagically allows users to sort by those types.
Long winded answer, but hope it helps.

Resources