Looping Through Multiple Arrays in Ruby - ruby-on-rails

I have multiple arrays of instances of ActiveRecord subclass Item that I need to loop through an print in accordance to earliest event. In this case, I need to print print out payment and maintenance dates as follows:
Item A maintenance required in 5 days
Item B payment required in 6 days
Item A payment required in 7 days
Item B maintenance required in 8 days
I currently have two queries for finding maintenance and payment items (non-exclusive query) and something like the following to output them:
<%- item_p = nil -%>
<%- item_m = nil -%>
<%- loop do -%>
<% item_p ||= #items_p.shift %>
<% item_m ||= #items_m.shift %>
<%- if item_p.nil? and item_m.nil? then break -%>
<%- elsif item_p and (item_m.nil? or item_p.paymt < item_m.maint) then -%>
<%= item_p.name %> payment required in ...
<%- elsif item_m and (item_p.nil? or item_m.maint < item_p.paymt) then -%>
<%= item_m.name %> maintenance required in ...
<%- end -%>
<%- end -%>
Any way to cleanup the readability of the above (ugly) code?

Embrace duck-typing and make sure that your objects are polymorphic. You want your payment items to be comparable with maintenance items, in order to sort them.
So, suppose you have a Payment and a Maintenance class:
module Due
include Comparable
# Compare this object with another. Used for sorting.
def <=>(other)
self.due <=> other.due
end
end
class Payment < ActiveRecord::Base
include Due
alias_method :due, :payment
def action
"#{name} requires payment"
end
end
class Maintenance < ActiveRecord::Base
include Due
alias_method :due, :maintenance
def action
"#{name} requires maintenance"
end
end
See how we create an action, due and <=> method in both classes? We also took care to include the Ruby built-in module Comparable. This allows us to do the following:
# Assuming 'payment' and 'maintenance' are date fields...
a = Payment.new :payment => 3.days.from_now
b = Maintenance.new :maintenance => 2.days.from_now
[a, b].sort
#=> [b, a]
The view then becomes as simple as:
<% (#payment_items + #maintenance_items).sort.each do |item| %>
<%= item.action %> in <%= distance_of_time_in_words_to_now(item.due) %><br/>
<% end %>
I'm sure I haven't got the details of your implementation right, but I hope this gives you an idea of how to approach your problem.

This is quick and dirty (i.e. not optimized):
# In your controller:
#items = #items_p.map{ |item| {:item => item, :days => item.paymt, :description => "payment"} }
#items += #items_m.map{ |item| {:item => item, :days => item.maint, :description => "maintenance"} }
#items = #items.sort_by{ |item| item[:day] }
# In your view:
<% #items.each do |item| %>
<%= item[:item].name %> <%= item[:description] %> required in <%= item[:days] %> days
<% end %>

You're doing way too much in your view. Really you should figure out all of this in the controller and pass through a cleaned up structure that you can iterate over for display purposes.
As an example:
length = [ #items_p.length, #items_m.length ].sort.last
#messages = [ ]
length.times do |i|
item_p = #items_p[i]
item_m = #items_m[i]
if (item_p and (item_m and item_p.paymt < item_m.maint) or !item_m)
#messages << "#{item_p.name} payment required in ..."
elsif (item_m and (item_p and item_m.maint < item_p.paymt) or !item_p)
#messages << "#{item_m.name} maintenance required in ..."
end
end
You would then iterate over #messages as required.
The real problem here is that you haven't structured these objects for this sort of thing strategically speaking. It would be nice if you had a single method for the due date instead of having to differentiate between paymt and maint according to the type. Likewise, it would be better if both of these were paired up into an Array instead of supplied separately.
If you had them in [ p, m ] pairs you could iterate much more simply:
items.each do |pair|
first_due = pair.compact.sort_by(&:due).first
#messages << "#{first_due.name} #{first_due.action} required in ..."
end
The action method would return payment or maintenance as required.

Related

Rails: How to pass params of multiple checkbox to the model

i built this form that generate me some chebox with value like "U6", "U8" eccc
<%= form.label "Seleziona Categorie" %>
<% TeamCategory::NAMES.each do |category| %>
<%= check_box_tag 'categories_selected[]', category -%>
<% end %>
Now i have to pass the value of selected check_box to a method in my model.
Now is:
def create_tournament_team_categories
TeamCategory::NAMES.each do |name|
team_category = TeamCategory.where(name: name).first_or_create
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
I would like to replace the TeamCategory::NAMES.each do with "selected check_box each do" and TeamCategory.where(name: name) with the value selected.
Thank you in advance
I am a newbie with Rails. What I see is that you took the part of the form to create the team, right?
For your code straight forward it could be:
<%= form.label "Seleziona Categorie" %>
<% TeamCategory::NAMES.each do |name| %> #you are looping through team category NAMES constant
<%= check_box_tag 'category_names_selected[]', name %>
<% end %>
Your form as is allows more than one category to be selected.
For the method:
def create_tournament_team_categories(category_names_selected)
category_names_selected.each do |name|
team_category = name
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
you will probably use this method in your teams_controller.rb. In the controller, you should be able to retrieve from params a freshly created array of selected names with something along the lines with this.
#category_names_selected = params[:category_names_selected]
I do not know how complicated your app is so it might also be nested under ["team"][:category_names_selected] or ["team"]["category_names_selected"] in your params hash.
To see the exact structure of the params hash and adjust the equation above you can add for example require 'pry' at the top of your controller file and then but the binding.pry just after the part where your method is executed. When you restart the server and the app hits this part of the controller you should be able to see the exact structure of your params hash in the terminal.
You can then pass the array to the method that you can call in the controller. Do not forget to add :category_names_selected to the strong params in the controller. I hope this helps.
Controller on line 30
def create
#tournament = Tournament.new(tournament_params)
#tournament.sport_club = current_user.sport_club
#category_names_selected = params[:category_names_selected]
if #tournament.save
redirect_to tournaments_path, notice: 'Torneo creato con successo'
end
end
Method create_tournament_team_categories in the model
after_create :create_tournament_team_categories
def create_tournament_team_categories(category_names_selected)
#category_names_selected.each do |name|
team_category = name
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end

In Ransack update ranackers dynamically when a model gets updated

I have Company Customer and CompanyCustomerField models. Customers store the hstore values in a column "properties" - the keys are from the CompanyCustomerField#name field. When a new CompanyCustomerField get created i need to add the #name to ransack to make them searchable.
When a new CompanyCustomerField gets added and I go to the search form I get
undefined method `*_cont' for #<Ransack::Search:0x00007ff670100978>
because the new field is not available for searching. If i shutdown my fails server and reboot it works because it gets it into ransack.
I don't know how to dynamically add the functionality into ransack. Any ideas greatly appreciated.
Customer.rb. this puts all the searchable fields into ransack but doesnt update it when new ones get added. because this only gets called once.
class Customer < ApplicationRecord
# ['favorite_color', 'receive_email_marketing' etc etc]
CompanyCustomerField.pluck(:name).each do |name|
ransacker name.to_sym do |parent|
Arel::Nodes::InfixOperation.new('->', parent.table[:properties], Arel::Nodes.build_quoted(name))
end
end
end
here is the search form:
#customers/index.html
<%= search_form_for #search, remote: true do |f| %>
<% current_company.customer_fields.each do |field| %>
<%= render "customers/search_fields/#{field.field_type}", f: f, field: field %>
<% end %>
<% end %>
#customers/search_fields/text_field
<%= f.label (field.name + "_cont").to_sym, field.name.humanize %>
<%= f.text_field (field.name + "_cont").to_sym %>
....
Even if moving reloading to controller, still same result.
CustomersController.rb
def index
Customer.reload_ransacker
#search = current_company.customers.includes(:owner).ransack(params[:q])
#customers = #search.result.page(params[:page])
end
Customer.rb
def self.reload_ransacker
puts "==="
puts "reload ransacker"
puts "==="
CompanyCustomerField.pluck(:name).each do |name|
ransacker name.to_sym do |parent|
Arel::Nodes::InfixOperation.new('->', parent.table[:properties], Arel::Nodes.build_quoted(name))
end
end
end
ActionView::Template::Error (undefined method `foo_cont' for #<Ransack::Search:0x00007fba3c05d5b8>):
SOLUTION:
needed to override:
module Ransack
module Adapters
module ActiveRecord
module Base
def ransacker(name, opts = {}, &block)
#ransackable_attributes = nil
self._ransackers = _ransackers.merge name.to_s => Ransacker
.new(self, name, opts, &block)
end
end
end
end
end
#ransackable_attributes needs to be reset to nil so in def ransackable_attributes(auth_object = nil) the instance var is nil and can reload
should be considered a bug in ransack.

Does ActiveRecord object not have instance variables for it's attributes but only methods?

In Rails I have the following model:
class Recipe < ActiveRecord::Base
attr_accessible :name , :description ,
:t_baking , :t_cooling , :t_cooking ,:t_rest
# other stuff here
end
with t_baking , t_cooling , t_cooking ,t_rest as Time.
So In my view I want to loop on each value.
<% ['cooking', 'baking', 'cooling' , 'rest'].each do |time| %>
<% time_of_recipe = #recipe.instance_variable_get("#t_#{time}") %>
<% if time_of_recipe.is_a? Time %>
<%= time_of_recipe.strftime "%H:%M" %>
<% else %>
<%= time_of_recipe %>
<% end %>
<% end %>
It doesn't work because
#recipe.instance_variable_get("#t_cooking").class # give NilClass
but
#recipe.t_cooking.class # give Time
Why?
#recipe.instance_variable_get("#t_cooking")
returns nil because there is no instance variable #t_cooking for #recipe.
It is a bunch of methods defined by ActiveRecord that you have access to, but these are not instance variables.
Applying it to your code, you'd want to change it to:
time_of_recipe = #recipe.public_send("t_#{time}")
Also, it is pointless to save single letter of typing.
It would be much more readable to do the following:
<% %w(t_cooking t_baking t_cooling t_rest).each do |time| %>
<% time_of_recipe = #recipe.public_send(time) %>
# ...
EDIT
If you want to check available instance variables (you look to be using Rails 3.2, so your output might be slightly different):
#recipe.instance_variables
#=> [:#attributes, <============ this one is of particular interest
# :#aggregation_cache,
# :#association_cache,
# :#readonly,
# :#destroyed,
# :#marked_for_destruction,
# :#destroyed_by_association,
# :#new_record,
# :#txn,
# :#_start_transaction_state,
# :#transaction_state
# ]
So you see, that it defines one instance variable #attributes, that holds all attributes.

if something list something with limit else list all in rails

I'm trying to figure out at this point how to display only a certain amount of categories based on a user plan. So in my controller I have the following;
def check_plan?
User.current.plan_id == "" && User.current.bins.count == 2 || User.current.plan_id == "nil" && User.current.bins.count == 2
end
helper_method :check_plan?
NOTE: I know, not pretty but it will do for now. So basically :check_plan? checks a few things.
Now I thought, that I could use that for our category list as a starting point, so that if the user isn't on any plan, they are limited in showing 2 if they are on a plan it shows them all. Here's what I thought would work but it doesn't. I've done it before but can't remember exactly how it went, so any help is appreciated.
<% if check_plan? %>
<% #bin_list.order("position").limit(2).each do |bin| %>
<% else %>
<% #bins_list.order("position").each do |bin| %>
<% end %>
Not really working and I know why, but their will all my other tries with bringing any of those lines together with check_plan.
Such logic belongs to Model natually. Don't abuse helper.
class User < ActiveRecord::Base
def bin_limit
check_plan? ? 0 : 2
end
def check_plan?
# Your logic
end
def bins_with_limit
bins.with_limit(bin_limit)
end
end
class Bin < ActiveRecord::Base
def self.with_limit(limit)
return self if limit <=0
self.limit(limit)
end
end
View
current_user.bins_with_limit.each do |bin|
First of all, the rails method .blank? will return true for either "" or nil, so you can start by refactoring your code as follows:
def check_plan?
( User.current.plan_id.nil? || User.current.plan_id.blank? ) && User.current.bins.count == 2
end
helper_method :check_plan?
Secondly, you're calling blocks in the code below, so you'll need to complete them with end
<% if check_plan? %>
<% #bin_list.order("position").limit(2).each do |bin| %>
<% end %>
<% else %>
<% #bins_list.order("position").each do |bin| %>
<% end %>
<% end %>
Of course, you'll want to put whatever you're doing with bin between the do and end portions above.

In ruby on rails, how to group records by tag when records have multiple tags

I'm using Rails 3.0 and the acts_as_taggable_on gem. I have a Candy model and candies can have multiple tags for flavors. Let's say
Candy1.tags #['apple', 'orange']
Candy2.tags #['orange', 'banana']
Candy3.tags #['apple', 'kiwi']
I want a list of tags with associated candies below them, like so:
Apple
Candy1
Candy3
Orange
Candy1
Candy2
...etc.
I've tried
Candy.all.group_by{ |candy| candy.tags }
but that treats the array of tags as a single entity, returning something like this:
['apple', 'orange']
Candy1
['orange', 'banana']
Candy2
Lacking a group_by_each method, whats the best way to accomplish this? Another Stack Overflow question explains how to do this in memory with simple hashes, but I wonder if there's a more database-oriented way to do it with ActiveRecord associations.
You can iterate over the candies and store them on a hash base on the tag:
grouped = {}
Candy.all.each do |candy|
candy.tags.each do |tag|
grouped[tag] ||= []
grouped[tag] << candy
end
end
At the end you will get:
{'apple' => [candy1, candy2], 'orange' => [] ...}
Hope this helps you
candies = Candy.all.inject({}) do |memo, candy|
candy.tags.each do |tag|
memo[tag] ||= []
memo[tag] << candy
memo
end
end
Now candies is a hash like this:
{
'apple' => [Candy1, Candy3],
'orange' => [Candy1, Candy2],
...
}
Which you can iterate over nicely:
candies.each do |key, value|
puts "Candy tag: #{key}"
value.each do |candy|
puts " * #{candy}"
end
end
In rails api documentation that group_by is for collecting enumerable. I think you need to try a more classic array iterator like the following since you've strings data
in views/candy/index.html.erb
<% #candys.each do |candy| %>
<%= candy.name %>
<% end %>
<%for tag in candy.tags %>
<li> <%= tag.name(or flavor %> </li> #tag.name_attribute
<% end %>
<% end %>
Let me know if it works

Resources