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.
Related
Filter with another table's attribute (one to many) in Rails
I'm a beginner on rails and currently working on my class project. I have a lead and a lead_comments table, lead has_many lead_comments and lead_comment belong_to lead, which I got those established.
In Model:
class Lead < ApplicationRecord
has_many :lead_comments
end
class LeadComment < ApplicationRecord
belongs_to :lead
end
In my lead's index view, I am trying to setup a date filter base on the Update_at in the lead_comments, which is the attribute from another table:
<div class="date-search">
<%= form_tag leads_path, method: :get do %>
<%= date_field_tag 'date_search[date_from]', #date_search.date_from %>
<%= date_field_tag 'date_search[date_to]', #date_search.date_to %>
<%= submit_tag 'Date Search' %>
<% end %>
</div>
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Phone Number</th>
<th>Last Updated Date</th>
<th colspan="3">Action</th>
</tr>
</thead>
<tbody>
<% #leads.each do |lead| %>
<tr>
<td><%= lead.lead_firstname %></td>
<td><%= lead.lead_lastname %></td>
<td><%= lead.lead_phone %></td>
<td><%= lead.lead_comments.maximum(:updated_at) %></td>
<td><%= link_to 'Detail', lead %></td>
<td><%= link_to 'Edit/Update', edit_lead_path(lead) %></td>
<td><%= link_to 'Remove', lead, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
In my lead controller, I set up another controller for lead search purpose:
def index
#date_search = LeadSearch.new(params[:date_search])
#leads = #date_search.scope
end
In my lead search model, I believe where my problem is:
class LeadSearch
attr_reader :date_from, :date_to
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from], 30.days.ago.to_date.to_s)
#date_to = parsed_date(params[:date_to], Date.today.to_s)
end
def scope
Lead.lead_comments.maximum(:updated_at).where('updated_at BETWEEN ? AND ?', #date_from, #date_to)
end
private
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
end
My lead_comment table only has one attributes, which is comment, along with the two automated date attributes created by Rails, created_at and updated_at.
Whenever I run the application I get an error that tells me undefined method "lead_comments". I thought I have already established the has_many and belong_to relationship and this still not working. Can someone please help me out and guide me to the right direction? Thank you for your time.
you are doing Lead.lead_comments which is wrong. You are using the class name instead of the object. There should be a Lead model's object.
#lead.lead_comments
First, try your query in rails console. then use it in the controller for better perspective. try following,
Lead.lead_comments # will give error
instead use,
Lead.last.lead_comments # success
Issues in your code:
Lead.lead_comments
Let's simplify this: think about lead_comments as a method that given a lead returns its comments. But we need a lead. That's why called on Lead does not work. That would work called on a Lead object.
Lead.lead_comments.maximum(:updated_at)
Here you're composing a SQL query though ActiveRecord but calling maximum stop this composition because that require a value so you're not able to call anything else after methods like maximum
Let's make your code work
def scope
Lead.where('updated_at BETWEEN ? AND ?', #date_from, #date_to)
end
in your view
<td><%= lead.lead_comments.maximum(:updated_at) %></td>
Note here that this code has always been correct because you call lead_comments on a lead object.
I know, this performs one query for each Lead object but to make it works with just one query looks like another question
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']
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 %>