Rails user defined views - ruby-on-rails

I'm trying to create user defined views in Rails.
I have a uview record for each view. It contains an hstore field called ufields. In ufields I'm storing the names of the columns to be used in the table view.
I can create the table's thead like this:
<thead>
<tr>
<% uview.ufields.each do |key, val| %>
<th><%= key %></th>
<% end %>
</tr>
</thead>
But, how can I define the fields for the tbody.
This doesn't work:
<tr>
<% uview.ufields.each do |key, val| %>
<% ufield = "vehicle." + key %>
<td><%= ufield %></td>
<% end %>
</tr>
That just puts out rows like this vehicle.name.
Is there a way to have this happen? <td><%= vehicle.name %>
This doesn't work, but might give you an idea of what I'm trying to do:
<td><%= <%= ufield %> %></td>

Try:
<% uview.ufields.each do |key, val| %>
<td><%= vehicle[key] %></td>
<% end %>
Actually you could be tempted to do:
vehicle.send key
vehicle.public_send key
But you'd expose to serious flaws since you trust user's inputs
An alternative would be to have in vehicle class:
WHITELISTED_USER_ATTRS = %w(name id) #add what you need but not stuff like destroy etc...
def user_input(key)
if WHITELISTED_USER_ATTRS.include?(key.to_s)
vehicle.send key
else
""
end
end
Then in the view:
<% uview.ufields.each do |key, val| %>
<td><%= vehicle.user_input(key) %></td>
<% end %>

Related

Removing conditional logic from a shared partial view or alternative solution

For a current project, I have duplicate code between views, and I'm not sure of the best route to refactor it.
I appear to be in a position where I can have duplicate code across various .html.erb files, or I could put identical code into a partial and use conditionals. I've always heard logic should stay out of views. Neither option seems ideal, and I don't currently know of alternatives.
To illustrate my question, I created a simple rails app called animals. I scaffolded for two models: one for cat and one for dog. Images display their corresponding attributes:
Displaying #cats and #dogs is pretty much the same. Cats just have a column for meows while Dogs have a column for barks, and a dog has the additional attribute column of plays_catch.
Lets say we choose to reduce the duplicate code for displaying cats and dogs by making a shared view partial:
#views/shared/_animal.html.erb
<tr>
<td><%= animal.name %></td>
<td><%= animal.age %> </td>
<% if animal.class == Cat %>
<td><%= animal.meows %> </td>
<% end %>
<% if animal.class == Dog %>
<td><%= animal.barks %> </td>
<td><%= animal.plays_catch %> </td>
<% end %>
</tr>
Then to render #cats = Cat.all:
<%= render partial: "shared/animal", collection: #cats %>
Then to render #dogs = Dog.all:
<%= render partial: "shared/animal", collection: #dogs %>
Obviously it would be overkill to do something like this for this specific example, but the real world project I'm applying it to would not be overkill.
The overall question is: how do you remove nearly identical code that iterates over collections, where the only difference is adding/removing a column of information? It just doesn't feel right to put that logic in the view itself, and leaving the duplication feels wrong.
You could use decorators and add methods that return the extra column(s):
class DogDecorator < Draper::Decorator
def extra_columns
[:barks, plays_catch]
end
end
class CatDecorator < Draper::Decorator
def extra_columns
[:meows]
end
end
...
<% animal.extra_columns.each do |column| %>
<td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% #cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: #cats %>
You can use respond_to? to solve the problem more generically. The view logic doesn't feel so wrong when it's more generic.
<% [:meows, :barks, :plays_catch].each do |method| %>
<% if animal.respond_to?(method) %>
<td><%= animal.send(method) %> </td>
<% end %>
<% end %>
You can add a method of the same name to both Cat and Dog classes which would return the specific instance attributes names and values. I'd recommend returning two arrays (one with the names of the fields, other with the fields' values, or vice-versa) since hashes are not exactly ordered. This way you can control the order in which they'll appear in the view.
For example:
#models/cat.rb
def fields_and_attributes
fields = ["Name","Age","Meows"]
attributes = [self.name, self.age]
if self.meows
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#models/dog.rb
def fields_and_attributes
fields = ["Name","Age","Plays catch"]
attributes = [self.name, self.age]
if self.plays_catch
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#controllers/animals_controller.rb
def display_animals
#animals = Cat.all + Dog.all # an array containing the different animals
end
#views/display_animals.html.erb
for i in (0...#animals.size)
fields_and_attributes = #animals[i].fields_and_attributes
for f in (0...fields_and_attributes[0].size)
<p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
end
end
Here, we first iterate over all of the animals and call the .fields_and_attributes method of that specific record; we then iterate over the results of calling that method, displaying fields and attributes in the same order as the one defined within the method and also guaranteeing that the code will display every field and every attribute regardless of the difference in the total number of fields for each different animal.
I don't know of any canonical way to accomplish this, but I would use one partial for this in the following way:
<tr>
<% animal.attributes.each do |_, value| %>
<td><%= value %></td>
<% end %>
</tr>
You can get rid of repeated attributes calls by providing in the partial a local variable with pre-obtained model attributes.
EDIT: if you only want to display some attributes.
# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>
<%= render partial: 'shared/animal',
collection: #dogs,
locals: {attrs: (#dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>
views/shared/_animal.html.erb:
<tr>
<% attrs.each do |attr| %>
<td><%= animal[attr] %></td>
<% end %>
</tr>
Below is my answer after reviewing posted answers. Basically:
I left the differences within each scaffold model's index page
I made shared partials for common table headers and table data
code below:
#app/views/cats/index.html.erb
<h1>Listing Cats</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Meows</th>
</tr>
</thead>
<tbody>
<% #cats.each do |cat| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
<td><%= cat.meows %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cat', new_cat_path %>
And for the dogs:
#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Barks</th>
<th>Plays catch</th>
</tr>
</thead>
<tbody>
<% #dogs.each do |dog| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
<td><%= dog.barks %></td>
<td><%= dog.plays_catch %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Dog', new_dog_path %>
The shared table headers for cats and dogs:
#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>
The shared table data for cats and dogs:
#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>

How to save a pair of ids with one check_box_tag in rails 3.2?

There are two sets of ids (id1 and id2) within one row on a form (form_tag), representing a pair of possible combination of id1 and id2 to choose. With check_box_tag, one id could be saved in an array like this:
<%= check_box_tag 'id_array[]', id1 %>
The id_array is returned as an array in params[:id_array]. Is there a way 2 ids can be saved with one check_box_tag (only need to check once)? We tried:
<%= check_box_tag 'id_array[][]', id1, id2 %>
and it did not work.
Update
Here is a single id form implemented:
<%= form_tag mass_onboard_user_accesses_path, :method => :put do %>
<tr>
<th></th>
<th><%= t('Engine Name') %></th>
<th><%= t('Engine Desp') %></th>
</tr>
<% #engines.each do |r| %>
<tr>
<% engine = Engine.find_by_id(r.resource_id)%>
<td><%= check_box_tag 'id_array[]', r.resource_id %></td>
<td><%= engine.name %></td>
<td><%= engine.module_desp %></td>
</tr>
<% end %>
<tr>
<th>
<%= submit_tag t('Save') ,:name => "save[#{#project_id}]" %>
</th>
</tr>
<% end %>
The only way I can think to do it is to cheat. I was trying to think how the collection of ids in a single checkbox would map to parameters in the URL and I failed to. So, the cheat is to assume that all params[:id_array] values are potentially an array of strings.
In your view:
<%= check_box_tag 'id_arrays[]', [id1, id2].join(",") %>
In your controller:
ids = []
params[:id_arrays].each do |sub_array|
ids += sub_array.split(",")
end
Or in a less verbose, and arguably less clear, form:
ids = params[:id_arrays].to_a.inject([]) {|c, id_array| c + sub_array.split(",") }
Except you'd probably encapsulate that in a method:
def multiple_id_param(param_name)
params[param_name].to_a.inject([]) do |c, id_array|
c + id_array.split(",")
end
end
ids = multiple_id_param(:id_array)
But then I fully appreciate that that's the kind of work you were trying to avoid by just calling the check_box_tag with some clever parameters. I guess this answer boils down to: "I don't think you can".
Although your use case is not absolutely clear to me, this might help:
<% Outerloop.each do |o| %>
<% Innerloop.each do |i| %>
<%= checkbox_tag "id_array[#{o.id}][]", i.id %>
<% end %>
<% end %>

Rails view: shuffle table column

I am having trouble figuring out how to shuffle table values in a view. I have a table in my view with a left and a right column, and would like to shuffle only the right column.
show.html.erb
<table>
<% #items.each do |item| %>
<tr>
<td><%= item.left %><td>
<td><%= item.right %><td>
</tr>
<% end %>
</table>
The "left" and "right" share the same primary id in the database. Any suggestions about how to shuffle only one side? Thanks!
You can use shuffle, do this way
<% shuffled_items = #items.shuffle %>
<% #items.each_with_index do |item, index| %>
<tr>
<td><%= item.left %><td>
<td><%= shuffled_items[index].right %><td>
</tr>
<% end %>
For details read this documentation http://ruby-doc.org/core-1.9.3/Array.html#method-i-shuffle
I think the simplest way is to have 2 arrays.
#items_left and #items_right
eg:
items = Item.a_scope
#items_left = items
#items_right = items.pluck(:right).shuffle #if you are on > rails 3.2
# #items_left = items.pluck(:left) #if only that attribute is needed
so you can use it as follows
<table>
<% #items_left.each_with_index do |item, i| %>
<tr>
<td><%= item.left %><td>
<td><%= #items_right[i] %><td>
</tr>
<% end %>
</table>

Show attribute value using its id

In my application, I am grouping my objects by an ID. At the moment, I can only display the ID, but I would like to display the attribute value.
A Fixture belongs_to a tournament and a tournament has_many fixtures.
Controller
def index
#fixtures = Fixture.all
#tournament_fixture = #fixtures.group_by {|f| f.tournament_id}
end
View
<% #tournament_fixture.sort.each do |tourn_name, fixture| %>
<%= tourn_name %>
<% fixture.each do |f| %>
<td><%= f.home_team %></td>
<td><%= f.away_team %></td>
<td><%= f.kickoff_time %></td>
</tr>
<% end %>
<% end %>
How can I get
<%= tourn_name %>
to display its corresponding value that is in its :name column?
At the moment in my view for example i get this returned
<tbody>
2
<tr>
<td>Tournament Name</td>
<td>Team 1</td>
<td>Team 2</td>
<td>2000-01-01 14:00:00 UTC</td>
<td><a class="btn btn-success" href="/fixtures/1">view</a></td>
</tr>
</tbody>
The 2 needs to be the value in the :name column
I'd recommend grouping by tournament instead:
#tournament_fixture = #fixtures.group_by(&:tournament)
And then iterate using:
<% #tournament_fixture.sort.each do |tournament, fixture| %>
<%= tournament.name %>
...
<% end %>
You can access the whole object much like you can get the id like this:
def index
#fixtures = Fixture.includes(:tournaments).all
#tournament_fixture = #fixtures.group_by {|f| f.tournament.name}
end
The id is still available as either f.tournament_id or f.tournament.id, should you still need it but I just figured you'd rather group by its name directly. I simply added an includes statement to also load the referenced Tournament objects with your fixtures in one go. Otherwise, Rails would load the tournaments only when you access them one by one.
As an alternative, you could load the Tournaments, including all their the fixtures instead and iterate over the tournaments like this:
Controller
def index
#tournaments = Tournament.includes(:fixtures).all
end
View
<% #tournaments.each do |tournament| %>
<%= tournament.name %>
<% tournament.fixtures.each do |f| %>
<td><%= f.home_team %></td>
<td><%= f.away_team %></td>
<td><%= f.kickoff_time %></td>
</tr>
<% end %>
<% end %>
It seems a bit more natural to me and you don't need to iterate over all fixtures to map them by their tournament.
You can load the fixtures in the right order. There is no need to group then in memory. Remember to include the tournaments to avoid N+1 queries.
# controller
def index
#fixtures = Fixture.order(:tournament_id).includes(:tournaments).all
end
Loading in the right order in the controller makes the view simpler. For the tournament's name just use the association between Fixture and Tournament.
# view
<% #fixtures.each do |fixture| %>
<tr>
<td><%= fixture.tournament.name %></td>
<td><%= fixture.home_team %></td>
<td><%= fixture.away_team %></td>
<td><%= fixture.kickoff_time %></td>
</tr>
<% end %>

Rails 3 each do ignore nil values

I am building an html table that should include name, rating1, rating2, and rating3. rating 1-3 come from different models than name.
resources :names do
resource :rat1,:rat2,:rat3
end
Inside of my html table I'd like to include the ratings from within each of these tables but I would like to automatically skip over or ignore tables that are nil. This is because :names may only have a :rat1 and not a :rat2 or :rat3. My view should look something like this.
<table>
<thead>Name</thead>
<thead>Rating 1</thead>
<thead>Rating 2</thead>
<thead>Rating 3</thead>
<% #names.each do |name| %>
<tr>
<td><%= name.nametext %></td>
<td><%= name.rat1.rating %></td>
<td><%= name.rat2.rating %></td>
<td><%= name.rat3.rating %></td>
</tr>
<% end %>
</table>
Except that if name.rat1 is nil it will either a.) replace the value with N/A OR b.) it will leave this field blank and move on to the next.
What is the cleanest way to do this?
::UPDATE::
So my issue is that the name.rat1 is nil and the name.rat1.rating is an undefined method of a nil class so both of these options will throw the same undefined method of a nil class error regardless of the || or helper method. At least thats what my current tests are showing. Any other options? or different workarounds? I'd like to avoid having to put a validation loop like this for every rat1-3
<% unless name.rat1.nil? %>
<%= name.rat1.rating %>
<% end %>
There has to be a simpler way.
I would probably create a helper method in names_helper.rb
def show_rating(rating)
if rating.present?
rating
else
"default value"
end
end
Then use it in the view:
<%= show_rating name.rat1.rating %>
OFFTOPIC Your table structure is wrong. It should have <thead><tr><th>Name</th><th>Rating1</th>..so on..</tr></thead>
So, in your case you can use the condition while rendering the rating values as:
<table>
<thead>
<tr>
<th>Name</th>
<th>Rating 1</th>
<th>Rating 2</th>
<th>Rating 3</th>
</tr>
</thead>
<tbody>
<% #names.each do |name| %>
<tr>
<td><%= name.nametext %></td>
<td><%= name.rat1.rating || 'N/A' %></td>
<td><%= name.rat2.rating || 'N/A' %></td>
<td><%= name.rat3.rating || 'N/A' %></td>
</tr>
<% end %>
</tbody>
</table>

Resources