Here is my controller:
def sedomain
#domain = params[:domain]
#virksomhed = Virksomhed.find(:all)
end
Virksomhed has_one Domain and Domain belongs_to Virksomhed.
That I am trying to achieve is looping through all rows in the domain table based on the params.
Here is a example for the dk domain page. It works fine.
<% #virksomhed.each do |virk| %>
<tr>
<td><%= virk.navn %></td>
<td><%= virk.domain.dk %> kr.</td>
<td><%= virk.domain.dkf %></td>
<td><%= virk.domain.dko %></td>
</tr>
<% end %>
But then I want to make it dynamic:
<% #virksomhed.each do |virk| %>
<tr>
<td><%= virk.navn %></td>
<td><%= virk.domain.#domain %> kr.</td>
<td><%= virk.domain.#domain %></td>
<td><%= virk.domain.#domain %></td>
</tr>
<% end %>
But I get a syntax error. I also think it is a bad solution because of users can access other columns in the Domain table via the params.
You can't dereference variables like this. What you mean to do is use send:
<td><%= virk.domain.send(#domain) %> kr.</td>
You will want to ensure that this user parameter conforms to a list of "known good" ones. There's two ways you might go about doing this, either pre-filtering and throwing an error if it's not valid, or having a method that eats the call quietly if it doesn't conform.
For instance:
#domain =
case(params[:domain])
when 'dk', 'dku', '...'
params[:domain]
else
raise "Hey, what are you doing?"
end
#tadman's answer is good, but i'd like to add my two cents. As #Emily said, you can store your whitelisted domains as a set in a Constant :
require 'set'
DOMAINS = Set.new( %W{dk dkf dko} )
As i understand it, your params[:domain] can either be an array or a string. to check if the domain is whitelisted, you can then do:
# ...
#domains= check_domains( params[:domain] )
# ...
def check_domains( dom )
case dom
when Array
dom if dom.to_set.subset?( DOMAINS )
else
[dom] if DOMAINS.include?( dom )
end
dom ||= []
end
then in your view (or maybe as some helper / partial):
<% if #domains.any? %>
<% #domains.each do |domain| %>
<td><%= virk.domain.send(domain) %> kr.</td>
<% end %>
<% end %>
Related
I want get the elements of array, call appliants, he are object from class services as show my code
<% #services.each do |service| %>
<tr>
<td><%= service.organ.id if service.organ %></td>
<td><%= service.id %></td>
<td><%= service['applicants'['type']] %></td>
</tr>
<% end %>
In this case I need to access the key type, but I don't have success.
applicants array - [{"type"=>"Titular da CNH.", "requirements"=>""}]
If you want all the type values then you can consider joining them in a sentence, use Array#map
<%= service.applicants&.map{ |applicant| applicant['type'] }&.to_sentence %>
More info on Array#map - https://www.geeksforgeeks.org/ruby-array-map-function/
Array#to_sentence - https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence
Give it a try!
I would avoid doing this in the view as your views should know as little as possible about the underlying models.
class Service
# #return [Array]
def applicant_types
(applicants||[]).map {|a| a["type"] }.compact
end
end
By always returning an array you can do service.applicant_types.each do |type| ... and be confident that it won't cause a nil error.
Hi I have a few chunks of codes which are badly written. I don't know how I should go about doing this.
Firstly there's a link which works fine:
<td><%= link_to 'Show', bidders_assignments_path(:assignment_id => assignment.id), :method => :post %></td>
That will link to the method bidders in assignment_controller:
def bidders
#bids = Bid.where(bidders_params).find_each
#I suspect there's error in the lines below
#bids.each do |bid|
#bidders = User.where(user.id => bid.user_id).find_each
end
end
def bidders_params
params.permit(:assignment_id)
end
Once the #bidders array is filled with data, it will be listed on the view:
<% #bidders.each do |bidder| %>
<tr>
<td><%= bidder.gender %></td>
<td><%= bidder.experience %></td>
<td><%= bidder.expected_salary %></td>
<td><%= bidder.education_id %></td>
<% end %>
I suspect the error is in the filling of #bidders array with data but I can't be sure that's why I'm here. Thanks in advance!
You need to make an SQL IN query. That will fix your code:
def bidders
bid_ids = Bid.where(bidders_params).pluck(:user_id)
#bidders = User.where(id: bid_ids)
end
Use instance variables to expose data from controller to view. If you need a variable to do some temporary calculations use local variables.
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>
In my class AleShot I have some dynamic mongoid-attributes. In order to index them, I collect all attributes in an array called "dynamos". Now when I want to list these (see code below) I get: undefined method 'dyn_f' for #<AleShot:0x007f8f7ab18328>
Any Ideas why the dyn_f-variable isn't translated correctly?
<% #ale_shots.each do |ale_shot| %>
<tr>
<td><%= ale_shot.name %></td>
<% dynamos.each do |dyn_f| %>
<td><%= ale_shot.dyn_f %></td>
<% end %>
</tr>
<% end %>
That could be because dyn_f is not defined as a field in the model.
Access it like this
ale_shot['dyn_f']
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 %>