Helper to translate multiple values - ruby-on-rails

I need to make a helper that takes multiple values related to a User and translate them and return them as a string to my view.
I was thinking about something like this:
in the helper:
def interests(user)
#all_interests = user.interests
#all_interests.each_with_index do |interest|
t('user.#{interest}') + ", "
if index == #all_interests.size - 1
t('user.#{interest}') + "."
end
end
In the view:
<p> My interests are: <%= interests(#user) %> </p>
The desired result would be for example:
My interests are: Engineering, Sports, Gardening.
But at this moment it returns an array like following:
My interests are: ["Engineering", "Sports", "Gardening"]
Edit: All the values are correctly translated in my i18n file.

You should map the results and join them with a ', ' in your case:
def interests(user)
user.interests.map {|interest| t("user.#{interest}") }.join(', ')
end
BTW, there is no need to set an instance variable. The helper method is returning the result already.

Better use array map+join:
def interests(user)
user.interests.map{|i| t("user.#{i}")}.join(', ')
end
And in view:
<p> My interests are: <%= interests(#user) %>. </p>

Related

Rails 4 how to calculate sum for dynamic queries

Having a challenge to calculate sum for dynamically built request.
I'm using each method to get value for each element and it work seamless.
<% params[:car].map{|n| n.first}.each do |p|%>
<%= #salon.price.send("price_" + p) %>
<% end %>
But then I'm trying to get sum for the same dynamically ("price_" + p) built queries it's failing.
<%= #salon.price.where("price_" + params[:car].map{|n| n.first}.to_s).all %>
Tried multiple solutions and no luck
You have where but haven't given it an actual where-like clause do you mean #salon.price.sum() instead? Otherwise what are you trying to filter on (where is for filtering, sum is for summation).
So what you seem to want to do is:
for all the prices for a given salon
sum up the columns price_0..price_n
right?
Now it'd be easy to construct a query to sum up the values for a single column
For that you'd try something like this:
<%= #salon.price.sum("price_0") %>
This uses the Rails sum method that works on any Active Record association.
And if you had a single price object and wanted to sum up all the price_X columns for that single price, you'd use something like this:
<%= params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum %>
This turns the numbers in params[:car] into an array of the column-values for the given price-object... then sums them at the end using the sum method that comes from the Array class (AKA Array#sum)
so to combine those two, you'd probably need something like this:
<%= #salon.prices.all.sum{|price| params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum } %>
Yes, that's a block inside a block.
Now it's possible that the Active Record version of sum can interfere with Array#sum and not like the above (which looks more like how you'd do the Array#sum). Rails' sum expects you to pass it the name of a single column like in the first example, rather than a block, like in the second example. So sometimes you then need to use map (which turns your set of values into an array) and then you can use Array#sum at the end like this:
<%= #salon.prices.all.map{|price| params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum }.sum %>
So that's a block (whose values are summed) inside another block (whose values are summed)
EDIT:
after discussion it seems you only have a single price record... and multiple columns on that single record... this changes things and makes them much simpler. You can just use this:
<%= params[:car].map{|n| #salon.price.send("price_" + n.first.to_s) }.sum %>
You use sum method for the array
sum = params[:car].map{|n| n.first}.sum
I'm guessing you're trying to get the sum of columns named price_n in the Price table where n is the value of params[:car].map(:&first). So I think the simple solution is:
<% params[:car].map(&first).each do |n| %>
<% sum += #salon.price.send("price_#{n.to_s") %>
<% end %>
<%= sum %>
But seeing the logic in the view is not a rails best practice, so it's better if we move the entire logic in your helper method. So in the view, just display this code:
<%= total_of_all_prices(params[:car], #salon.price) %>
Then in your helper method add this method
def total_of_all_prices(car_params, price_object)
sum = 0
car_params.map(&:first).each do |n|
sum += price_object.send("price_#{n.to_s}")
end
sum
end

Grouping Sunspot Models to get sorting/count on View

#search = Sunspot.search(Event, Person, Organization) do
keywords params[:q]
order_by(:score)
end
Based on the search results I'd like to create a list of Models with counts for each model.
Events (12)
People (5)
Organizations (3)
Is there a way to do this type of grouping in Sunspot?
<% #search.each_hit_with_result do |hit, result| -%>
<%= result.class %> <!- Gives me Model, but repeated -->
<% end %>
There is probably a smarter way, but one possible way is to get array of the classes like this
classes = #search.results.map(&:class) # this will give array of returned classes
then do as suggested in this link
h = Hash.new(0)
classes.each { | v | h.store(v, h[v]+1) }
# h = { 3=>3, 2=>1, 1=>1, 4=>1 }
hope that helps

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

Alphabet Filter for Table in Rails

I'm new to rails, so forgive me if there is an easy answer. I'm trying to implement an "Alphabet Index" for a table in rails. I want to be able to click on a letter and have the table refresh - containing only the data on which the first letter of the last_name column matches the letter clicked.
My first thought was to generate an array of letter from the database
#visitor_array = []
#v = Visitor.all
#v.each do |visitor|
#visitor_array << visitor.last_name[0,1]
end
#visitor_array.sort!
#visitor_array.uniq!
This code gives me an array which has one of each of the first letters. I used this as my "alphabet". On my view I have the following code
<% #visitor_array.each do |visitor| %>
<%= link_to visitor, alphasort(visitor) %>
<% end %>
The thought here is to run a helper function when the letter is clicked. But here is where I'm stuck.
UPDATE:
Okay, thanks to the comments below, I was able to figure it out. In case anyone else was wondering, this is how it was accomplished.
CONTROLLER
# Create array consisting of the first letter of every visitor's last name
#visitor_array = []
#v = Visitor.all
#v.each do |visitor|
#visitor_array << visitor.last_name[0,1]
end
#Sort array alphabetically in ASC order
#visitor_array.sort!
#Remove duplicate elements
#visitor_array.uniq!
if (params[:letter] != nil)
#visitors = Visitor.order("last_name ASC").where("last_name like ?", params[:letter] +"%" )
else
#visitors = Visitor.order("last_name ASC")
end
VIEW
<% #visitor_array.each do |letter| %>
<%= link_to letter, :controller => "visitors" , :letter => letter %>
<% end %>
Use this to get the list of results for a specific letter:
visitors = Visitor.order("last_name ASC").where("last_name like '?'", letter)
Then on your view:
<% #visitor_array.each do |visitor| %>
<%= link_to visitor(visitor) %>
<% end %>
The syntax might be slightly off but the fundamental idea is there.
I'm not entirely sure what you mean by the last line though...

Does this code belong in the model or controller

To start, I'm a pretty new to Rails
I've created some methods and put them into my model, but it looks messy and just wondered if the code belongs in the model or the controller? What makes my code unique (not one model per controller anyhow) is that I have only one model "Products" but have 3 controllers that interact with it, "Merchants, Categories, Brands". Maybe there is an easier way I have completely overlooked?? I don't really want to split the data up into 3 tables / models with links between.
p.s. This is the first time I have slipped away from the comfort of a Rails book so please go easy on me! Any other general suggestions to my code will be much appreciated.
Product model
class Product < ActiveRecord::Base
validates :brand, :presence => true
def product_name
name.capitalize.html_safe
end
def product_description
description.html_safe
end
#Replace '-' with ' ' for nice names
def brand_name
brand.capitalize.gsub('-',' ')
end
def category_name
category.capitalize.gsub('-',' ')
end
def merchant_name
merchant.capitalize.gsub('-',' ')
end
#Replace ' ' with '-' for urls
def brand_url
brand.downcase.gsub(' ','-')
end
def category_url
category.downcase.gsub(' ','-')
end
def merchant_url
merchant.downcase.gsub(' ','-')
end
end
Merchants Controller
class MerchantsController < ApplicationController
def index
#merchants = Product.find(:all, :select => 'DISTINCT merchant')
end
def show
#products = Product.find(:all, :conditions => ['merchant = ?', params[:merchant]])
#merchant = params[:merchant].capitalize.gsub('-',' ')
end
end
Merchant view (index)
<h1>Merchant list</h1>
<%= #merchants.count%> merchants found
<% #merchants.each do |merchant| %>
<p><%= link_to merchant.merchant_name, merchant.merchant_url %></p>
<% end %>
Merchant view (show)
<h1>Products from merchant: <%= #merchant %></h1>
<%= #products.count%> products found
<% #products.each do |product| %>
<h3><%= product.product_name %></h3>
<p>
<img src="<%= product.image %>" align="right" alt="<%= product.product_name %>" />
<%= product.product_description %>
</p>
<p><%= product.price %></p>
<p>Brand: <%= product.brand_name %></p>
<p>Category: <%= product.category_name %></p>
<p>Sub category: <%= product.sub_category %></p>
<p>Merchant: <%= product.merchant_name %></p>
<p>More information</p>
<hr />
<% end %>
So your data model does seem to be getting to the point where you might at least want to split merchants out. You can tell this from the select 'DISTINCT merchant' query. If your merchants are user-based input and are saved inside your products table it seems like a good time to move them into their own model so that they are easily searchable and manageable. As you get more merchants and more products it will get harder and harder to perform this query. Once you want to add additional merchant information you'll be in a worse position as well. Just keep in mind Rails was made for easy refactoring. Making this change shouldn't be daunting, it should just be another regular task in your agile development process.
What the above change would also allow you to do is change these lines:
#products = Product.find(:all, :conditions => ['merchant = ?', params[:merchant]])
#merchant = params[:merchant].capitalize.gsub('-',' ')
into:
#merchant = Merchant.find_by_name(params[:name])
#products = #merchant.products
You could then have a capitalize and gsub name with a model function:
#merchant.display_name
The next step would be to DRY up your model code a little bit, for example:
class Product
def brand_name
make_name brand
end
def category_name
make_name category
end
def merchant_name
make_name merchant
end
private
def make_name name
name.capitalize.gsub('-', ' ')
end
end
You could do something similar to the _url functions as well. If you wanted to venture further you could clean this up using meta-programming as well.
Final Thoughts: make sure you actually want to be calling html_safe on your strings. If they are user based input it's best to let them go through the h function in your views. Do you want users to be able to enter HTML strings as brands, merchants and categories? If so, then leave the html_safe string there, otherwise let the strings be made html_safe in your views.
In general you are on the right path: Skinny Controllers and Views and Fat Models is the way to go. This means put your logic and your heavy-lifting into your Models and let your Controllers and Views be small and simple.
You should probably normalise your database. You need 3 tables instead of one: Products, Merchants and Brands. Your product table will then have references to merchant and brand tables. You can then have separate models (with belongs_to/has_many relationships between them) and separate controllers.
You will still be able to write things like product.merchant.name but some of your code will be simpler.
Conventions are just that, conventional. There is no right or wrong no matter who in Atlanta tells you that there are. F#$k him.
Anyways, if you are going with a Skinny Controller Fat model, then yeah, you're on the right track.
As they say, do all the heavy lifting in your model.
I'd look to refactor those methods personally in the model. All those places where you are calling *.downcase.gsub...
Also look into to_param, a method you can overwrite to get purdy urls.

Resources