Rails loop multiple instance variables on same view - ruby-on-rails

Let say I have a controller Transactions:
#transactions = Transaction.all.group(:type)
#transaction_date_asc = Transaction.all.order(:DATE => :desc).group(:type)
#transaction_date_desc = Transaction.all.order(:DATE => :asc).group(:type)
In my view I need to loop all 3 instances.
Something like this where I want to show newest and oldest amount or discount for each transaction type.
<% #transactions do |transaction|%>
transaction.type.name
<%end%>
<% #transaction_date_asc do |transaction_asc|%>
transaction_asc.amount
<%end%>
<% #transaction_date_desc do |transaction_desc|%>
transaction_desc.amount
<%end%>
<% #transaction_date_asc do |transaction_asc|%>
transaction_asc.discount
<%end%>
<% #transaction_date_desc do |transaction_desc|%>
transaction_desc.discount
<%end%>
How am I supposed to place loops, columns and <%end> in my view?

Someone may come up with a better solution, but from my seat it looks like you need to do a query for each group in your view to get the first date transaction, but on the plus side, you only need one query in the controller for the transactions with the last date.
#transactions = Transaction.group(:type).having('DATE = MAX(DATE)')
In the view...
<% #transactions do |transaction| %>
<% first_transaction = Transaction.where(type: transaction.type).order('transaction_date').first %>
<%= transaction.type.name %>
<%= first_transaction.amount %>
<%= transaction.amount %>
<%= first.transaction.discount %>
<%= transaction.discount %>
<% end %>
However... to keep the logic in the view cleaner you could have an instance method for transaction types that will return the first and last transaction.
class Type << ActiveRecord::Base
def first_transaction
Transaction.where(type: self).order('transaction_date ASC').first
end
def last_transaction
Transaction.where(type: self).order('transaction_date DESC').first
end
end
Then in the controller...
#types = Type.all
then in the view...
<% #types.each do |type| %>
<%= type.name %>
<%= type.first_transaction.try(:amount) %>
<%= type.last_transaction.try(:amount) %>
<%= type.first_transaction.try(:discount) %>
<%= type.last_transaction.try(:discount) %>
<% end %>
The reason I'm suggesting #try is to handle the case of no transactions being present for a specific type.

Related

Ruby on Rails: Why empty value is displayed when form for creation of new objects in DB is included?

I am newbie. I am trying to develop a simple web application involving shops and candies where a shop can have many candies.
I have the following code in my shops/show.html.erb which displays list of candies twice.
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
<%= form_for([#shop, #shop.candies.build]) do |f| %>
<%= render(:partial => 'form',
:locals => {:f => f, :header => "Add a Candy",
:placeholder => "Enter a candy name"}) %>
<% end %>
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
My code in _form.html.erb for creating a new candy:
<%= f.text_field(:name, :placeholder=> placeholder, :class=>"form-control custom-input")%>
<%= button_tag( :class => "btn btn-primary mb-2 btn-custom btn-custom-sc") do %>
<i class="fas fa-plus icon"></i>
<% end %>
Code of Shops Controller:
class ShopsController < ApplicationController
def show
#shop = Shop.find(params[:id])
#unshelved_candies = #shop.candies.unshelved_candies
end
private
def shop_params
params.require(:shop).permit(:name)
end
end
Code of Candies Controller:
class CandiesController < ApplicationController
def create
#shop = Shop.find(params[:shop_id])
#candy = #shop.candies.create(candy_params)
redirect_to(shop_path(#shop))
end
private
def candy_params
params.require(:candy).permit(:name)
end
end
end
When I run the code and view it on browser, I notice that it creates an empty candy in the second loop (not in database). However, when I remove the form for creating candies, it behaves as it should. I am unable to understand why it's looping one more time and displaying blank value.
The output of the first loop is the correct one:
Candy 1
Candy 2
Candy 3
And the output of second loop is:
Candy 1
Candy 2
Candy 3
[<---Empty. I am not inserting anything new to the database]
Can anybody tell me why it is displaying a blank value in second loop and how to prevent this extra iteration?
I believe the "extra" candy is the one you're instantiating here:
<%= form_for([#shop, #shop.candies.build]) do |f| %>
The candy name for the new candy is nil, so you're getting the blank.
BTW, this:
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
Strikes me as non-idiomatic ruby. I would expect to see something more like:
<% #shop.candies.each.with_index(1) do |candy, index| %>
<%= index %> <%= candy.name %>
<% end %>
I guess a brute force way of making sure you don't get that extra candy would be to do something like:
<% #shop.candies.each.with_index(1) do |candy, index| %>
<% unless candy.new_record? %>
<%= index %> <%= candy.name %>
<% end %>
<% end %>
You might also try:
<%= form_for([#shop, #candy]) do |f| %>
Which I believe can be written:
<%= form_for(#shop, #candy) do |f| %>
If you want to save yourself a couple of key strokes (they add up over time).
And then in your ShopsController do:
class ShopsController < ApplicationController
def show
#shop = Shop.find(params[:id])
#candy = Candy.new
#unshelved_candies = #shop.candies.unshelved_candies
end
private
def shop_params
params.require(:shop).permit(:name)
end
end
This is also nice because it avoids:
#shop.candies.build
Which requires that your view knows a lot about the relationship between shop and candies and also requires that your view interacts directly with the database.
Since you're apparently using nested routes, you might want to look at the shallow: true directive.
Also (this is not related to your question), you might want to be thoughtful about the Law of Demeter. I notice you do:
#unshelved_candies = #shop.candies.unshelved_candies
Personally, I would do something more like:
#unshelved_candies = #shop.unshelved_candies
And in Shop, you might have something like:
class Shop < ApplicationRecord
def unselved_candies
candies.unshelved
end
end
And in Candy, something like:
class Candy < ApplicationRecord
class < self
def unshelved
where(shelved: false) # or however you determine a candy is unshelved
end
end
end
Many people would make unshelved a scope, which is another way of doing the same thing.
This way, your ShopsController knows less about the mechanics of the relationships between shops and candies and shelved status. FWIW.

RoR iterate through a group/each_slice

I'm looking to get the last 5 items in an array, but to display each item individually, not as a group.
Here's what I've tried below with no success.
#array = Town.all
<% #array.each_slice(5) do |a| %>
<%= a.first %>
<%= a.second %>
<%= a.third %>
<%= a.fourth %>
<%= a.fifth %>
<% end %>
I can't figure out how to pull out a specific number of items and then to call each individual item.
The method you are looking for is in_groups_of (https://apidock.com/rails/Array/in_groups_of):
<% #towns.in_groups_of(5) do |five_towns| %>
<% five_towns.each do |town| %>
# [...]
<% end %>
<% end %>
Update: Maybe you want only the last 5 elements of the array. If so, you can simply call:
Town.all.last(5)
# => array of (max) 5 Town records
To get the last 5 items of an Array you could use last and then iterate them with each:
<%= #array.last(5).each do |a| %>
...
<% end %>
But it seems that you want to get the last 5 records of a model; in that case, you could try this instead:
#array = Town.limit(5).order('id desc')
<%= #array.each do |a| %>
...
<% end %>
... is there a way that I can have let's say the last 5 records of a
model all within one loop? maybe
<%= #array.each do | a, b, c, d, e| %>
<%= a.description %>
<%= b.description %>
<% end %>
You could use the index of each item instead of using each; for example:
#last_five = #array.last(5)
<%= last_five[0] %>
<%= last_five[1] %>
<%= last_five[2] %>
<%= last_five[3] %>
<%= last_five[4] %>
Although that wouldn't be DRY, so it will be better to stick with each.

undefined method `value' for #<CategoryItemValue::ActiveRecord_Associations_CollectionProxy:0x007ff9706d24c0>

This is so simple but isnt working. What am I missing?
controlelr
#guide = Guide.friendly.find(params[:guide_id])
#category = #guide.categories.friendly.find params[:id]
#items = #category.category_items
view
<% #items.each do |item| %>
<%= item.category_item_values.value %>
<% end %>
gives the no method error of
undefined method 'value' for #<CategoryItemValue::ActiveRecord_Associations_CollectionProxy:0x007ff9706d24c0>
There is a values column in the category_item_values table so I'm not sure what the problem is.
item.category_item_values is the CollectionProxy instance (one might think of it as of an kinda array.)
Each category_item has [likely, you did not provide sufficiently enough info to guess more precisely] many values. If the assumption above is correct, here you go:
<% #items.each do |item| %>
<% item.category_item_values.each do |value| %>
<%= value %> # or maybe (depending on your model) <%= value.value %>
<% end %>
<% end %>
You will have to loop over each of the category_item_values to get the result as this suggests <CategoryItemValue::ActiveRecord_Associations_CollectionProxy:0x007ff9706d24c0> that your category_item_value is a association.
So you could do something like
<% item.category_item_values.each do |category_item_value| %>
<%= category_item_value.value %>
<% end %>

How can I loop through and Save values into an array

The following loop goes through the sales column and lists all 4 existing product values, like 19.99 19.99 3.99 3.99 to the corresponding user id.
<% #sales.each_with_index do |sale, index| %>
<% if current_user.id == sale.user_id %>
<% price = Warehouse.where(:product => sale.product).pluck(:mrr) %>
<%= value = price.split(',').join('.').to_f %>
<% else %>
<% end %>
Now I want to save the results/values into a new global variable and add up each out of "value". So the result of 19.99 19.99 3.99 3.99 should be 47.96.
I'm completely lost. Any Ideas?
You could do something like this:
<% total = 0 %>
<% #sales.each_with_index do |sale, index| %>
<% if current_user.id == sale.user_id %>
<% price = Warehouse.where(:product => sale.product).pluck(:mrr) %>
<%= value = price.split(',').join('.').to_f %>
<% total += value %>
<% end %>
<% end %>
<%= "Total is #{total}" %>
It is highly questionable to have code like this in the view though. You could get prices and calculate totals in your controller instead.
Also note that you are missing an end. I changed the unneeded else to an end.
In your controller you can create a instant variable prefixed by # so it can be used throughout your view
For example in your controller
#total_value = 0
And in your view
<%#sales.each_with_index do |sale, index| %>
<% if current_user.id == sale.user_id %>
<% price = Warehouse.where(:product => sale.product).pluck(:mrr) %>
<%= value = price.split(',').join('.').to_f %>
<% #total_value += value %>
<% else %>
<% end %>
You shouldn't add that kind of logic in your view. Create a view object class (that the controller instantiates) too handle all of this. You also probably can do something like:
user.sales.each do |sale|
total += find_price(sale)
# do more stuff
end
If you are asking 'if current_user.id == sale.user_id' then you most likely doing it wrong.
In that view object you could have a hash that has all the prices you want to show and iterate over that in your view.

If two DateTime stamps are the same in Rails

I have created a simple appointment system, and I now need to display something inside a loop if there's two or more appointments with the same date and time. The appointments are displayed in order of time, so they're just appearing one after the other.
Controller
def index
#todays_apps = current_user.appointments.order(time ASC)
end
View
<% #todays_apps.each do |app| %>
<%= app.business_name %>
<%= app.business_address %>
<%= app.time %>
<% end %>
I'm looking to display a message or icon the appointment shares a date and time with another appointment. Tried a collection of things with no luck.
You can group your collection by time and modify your iteration accordingly. You can group it like
#todays_apps.group_by(&:time)
The outcome will be something like
=> { timestamp1 => [app1,app2], timestamp2 => [app3], timestamp3 => [app4]}
Or you can try a quick hacky way like:
<% previous_time = nil %>
<% #todays_apps.each do |app| %>
<%= app.business_name %>
<%= app.business_address %>
<%= 'Your message or class or anything' if previous_time == app.time %>
<%= previous_time = app.time %>
<% end %>
Try Like this:
controller:
def index
#appointments = current_user.appointments.order("time ASC")
#todays_apps = #appointments.group_by(&:time)
end
View:
<% #todays_apps.each do |time, appointments| %>
<%= time %>
<% appointments.each do |appointment| %>
<%= appointment.business_name %>
<%= appointment.business_address %>
<% end %>
<% end %>
It will list all the appointments for particular time.
Thanks

Resources