get models name and count - ruby-on-rails

I have this in models_name:
model_names = Object.constants.collect { |sym| Object.const_get(sym) }.select { |constant| constant.class == Class && constant.include?(Mongoid::Document) }.collect { |klass| klass.name }
the result is:
["Model_name1","Model_name2","Model_name3"]
I need something like:
[{"Model1":"Count"},{"Model2":"Count"},{"Model3":"Count"}]
I need show in a chart all models and object counter inside each model is working with morris.js
You can see the example:
http://jsbin.com/uzosiq/2/embed?javascript,live
Thank you very much!

that last one:
collect { |klass| klass.name }
rewrite it as
collect { |klass| [klass.name, klass.count] }
this should return an array of arrays of 2 elements (classname and count). If the variable to which this is assigned is called a, just do this:
Hash[a]
now you have a hash at your disposal. Now you can do this:
Hash[a].map do |k, v|
{k => v}
end
and now you have an array of hashes of only one key-value assoc each. Which I think it is what you want.

module Foo
end
class Bar
include Foo
end
1.upto(5) { Bar.new }
model_names = Object.constants.collect { |sym| Object.const_get(sym) }.
select { |constant| constant.class == Class && constant.include?(Foo) }.
inject({}) do |m,klass|
m.update klass.name => ObjectSpace.each_object(klass).to_a.size
end
p model_names
{"Bar"=>5}
Will work only on MRI based rubies, so no jRuby nor Runbinius.
Here is a live demo

Related

build a new array of hash from multiple array of hashes

I have following three array of hashes.
customer_mapping = [
{:customer_id=>"a", :customer_order_id=>"g1"},
{:customer_id=>"b", :customer_order_id=>"g2"},
{:customer_id=>"c", :customer_order_id=>"g3"},
{:customer_id=>"d", :customer_order_id=>"g4"},
{:customer_id=>"e", :customer_order_id=>"g5"}
]
customer_with_products = [
{:customer_order_id=>"g1", :product_order_id=>"a1"},
{:customer_order_id=>"g2", :product_order_id=>"a2"},
{:customer_order_id=>"g3", :product_order_id=>"a3"},
{:customer_order_id=>"g4", :product_order_id=>"a4"},
{:customer_order_id=>"g5", :product_order_id=>"a5"}
]
product_mapping = [
{:product_id=>"j", :product_order_id=>"a1"},
{:product_id=>"k", :product_order_id=>"a2"},
{:product_id=>"l", :product_order_id=>"a3"}
]
What i want is a new hash with only customer_id and product_id
{:product_id=>"j", :customer_id=>"a"},
{:product_id=>"k", :customer_id=>"b"},
{:product_id=>"l", :customer_id=>"c"}
I tried to loop over product_mapping and select the customer_order_id that match product_order_id in customer_with_products and then thought of looping over customer_mapping but not able to get desired output from the first step.
How can i achieve this?
Using
def merge_by(a,b, key)
(a+b).group_by { |h| h[key] }
.each_value.map { |arr| arr.inject(:merge) }
end
merge_by(
merge_by(customer_mapping, customer_with_products, :customer_order_id),
product_mapping,
:product_order_id
).select { |h| h[:product_id] }.map { |h| h.slice(:product_id, :customer_id) }
#=>[{:product_id=>"j", :customer_id=>"a"},
# {:product_id=>"k", :customer_id=>"b"},
# {:product_id=>"l", :customer_id=>"c"}]
Definitely not the cleanest solution, if your initial arrays come from SQL queries, I think those queries could be modified to aggregate your data properly.
merge_by(customer_mapping, customer_with_products, :customer_order_id)
# => [{:customer_id=>"a", :customer_order_id=>"g1", :product_order_id=>"a1"},
# {:customer_id=>"b", :customer_order_id=>"g2", :product_order_id=>"a2"},
# {:customer_id=>"c", :customer_order_id=>"g3", :product_order_id=>"a3"},
# {:customer_id=>"d", :customer_order_id=>"g4", :product_order_id=>"a4"},
# {:customer_id=>"e", :customer_order_id=>"g5", :product_order_id=>"a5"}]
Then merge it similarly with your last array and cleanup the result selecting only the elements for which :product_id was found, slicing wanted keys.
Alternatively, a much more readable solution, depending on your array sizes might be slower as it keeps iterating over the hashes:
product_mapping.map do |hc|
b_match = customer_with_products.detect { |hb| hb[:product_order_id] == hc[:product_order_id] }
a_match = customer_mapping.detect { |ha| ha[:customer_order_id] == b_match[:customer_order_id] }
[hc, a_match, b_match].inject(:merge)
end.map { |h| h.slice(:product_id, :customer_id) }
Following your handling of the problem the solution would be the following:
result_hash_array = product_mapping.map do |product_mapping_entry|
customer_receipt = customer_with_products.find do |customer_with_products_entry|
product_mapping_entry[:product_order_id] == customer_with_products_entry[:product_order_id]
end
customer_id = customer_mapping.find do |customer_mapping_entry|
customer_receipt[:customer_order_id] == customer_mapping_entry[:customer_order_id]
end[:customer_id]
{product_id: product_mapping_entry[:product_id], customer_id: customer_id}
end
Output
results_hash_array => [{:product_id=>"j", :customer_id=>"a"},
{:product_id=>"k", :customer_id=>"b"},
{:product_id=>"l", :customer_id=>"c"}]
Other option, starting from customer_mapping, one liner (but quite wide):
customer_mapping.map { |e| {customer_id: e[:customer_id], product_id: (product_mapping.detect { |k| k[:product_order_id] == (customer_with_products.detect{ |h| h[:customer_order_id] == e[:customer_order_id] } || {} )[:product_order_id] } || {} )[:product_id] } }
#=> [{:customer_id=>"a", :product_id=>"j"},
# {:customer_id=>"b", :product_id=>"k"},
# {:customer_id=>"c", :product_id=>"l"},
# {:customer_id=>"d", :product_id=>nil},
# {:customer_id=>"e", :product_id=>nil}]
cust_order_id_to_cust_id =
customer_mapping.each_with_object({}) do |g,h|
h[g[:customer_order_id]] = g[:customer_id]
end
#=> {"g1"=>"a", "g2"=>"b", "g3"=>"c", "g4"=>"d", "g5"=>"e"}
prod_order_id_to_cust_order_id =
customer_with_products.each_with_object({}) do |g,h|
h[g[:product_order_id]] = g[:customer_order_id]
end
#=> {"a1"=>"g1", "a2"=>"g2", "a3"=>"g3", "a4"=>"g4", "a5"=>"g5"}
product_mapping.map do |h|
{ product_id: h[:product_id], customer_id:
cust_order_id_to_cust_id[prod_order_id_to_cust_order_id[h[:product_order_id]]] }
end
#=> [{:product_id=>"j", :customer_id=>"a"},
# {:product_id=>"k", :customer_id=>"b"},
# {:product_id=>"l", :customer_id=>"c"}]
This formulation is particularly easy to test. (It's so straightforward that no debugging was needed).
I would recommended to rather take a longer but more readable solution which you also understand in some months from now by looking at it. Use full names for the hash keys instead of hiding them behind k, v for more complexe lookups (maybe its just my personal preference).
I would suggest somethink like:
result = product_mapping.map do |mapping|
customer_id = customer_mapping.find do |hash|
hash[:customer_order_id] == customer_with_products.find do |hash|
hash[:product_order_id] == mapping[:product_order_id]
end[:customer_order_id]
end[:customer_id]
{ product_id: mapping[:product_id], customer_id: customer_id }
end

How to dynamically call scopes with 'OR' clause from an array

I have an array, and I'd like to call scopes with OR clause:
cars = ['bmw', 'audi', 'toyota']
class Car < AR
scope :from_bmw, -> { where(type: 'bmw') }
scope :from_audi, -> { where(type: 'audi') }
scope :from_toyota, -> { where(type: 'toyota') }
end
I'd like to achieve something like this:
Car.from_bmw.or(Car.from_audi).or(Car.from_toyota)
My cars array can change; in case: cars = ['toyota', 'audi'], my method should produce:
Car.from_toyota.or(Car.from_audi)
I have something like the following:
def generate(cars)
scopes = cars.map {|f| "from_#{f} "}
scopes.each do |s|
# HOW TO I ITERATE OVER HERE AND CALL EACH SCOPE???
end
end
I don't want to pass type as an argument to scope, there's a reason behind it.
def generate(cars)
return Car.none if cars.blank?
scopes = cars.map {|f| "from_#{f} "}
scope = Car.send(scopes.shift)
scopes.each do |s|
scope = scope.or(Car.send(s))
end
scope
end
Assuming the given array contains only valid type values, you could simply do that:
class Car
scope :by_type, -> (type) { where(type: type) }
end
types = ['bmw', 'audi', 'toyota']
Car.by_type(types) # => It'll generate a query using IN: SELECT * FROM cars WHERE type IN ('bmw', 'audi', 'toyota')
If you don't want to pass the array as an argument to scope for whatever reason, you could create a hash mapping the array values to valid by_type arguments.
VALID_CAR_TYPES = { volkswagen: ['vw', 'volkswagen'], bmw: ['bmw'], ... }
def sanitize_car_types(types)
types.map do |type|
VALID_CAR_TYPES.find { |k, v| v.include?(type) }.first
end.compact
end
Car.by_type(sanitize_car_types(types))

Sorting by integers represented as strings

I would like sort array of ActiveRecord objects by related object's attribute value. Meaning something like this:
Item has one product which has an attribute SKU. The SKU is mostly integer stored as a string, but could be alphanumeric as well.
sorted = items.sort_by { |item| Integer(item.product.sku) } rescue items
For now in case of error the items with original order returns.
What would I like to do?
Extend the Array class to achieve something like:
items.numeric_sort { |item| item.product.sku }
What I did so far?
1. Building a lambda expression and passing it
class Array
def numeric_sort(&lambda)
if lambda.respond_to? :call
self.sort_by(&lambda) rescue self
else
self.sort_by { |el| Integer(el) } rescue self
end
end
end
product_bin = lambda { |task_item| Integer(item.product.bin) }
items.numeric_sort(&product_bin)
2. Building lambda expression from methods chain
class Object
def send_chain(keys)
keys.inject(self, :send)
end
end
class Array
def numeric_sort_by(*args)
(args.length == 1) ? lam = lambda {|el| Integer(el.send(args))} : lam = lambda {|el| Integer(el.send_chain(args))}
self.sort_by(&lam) rescue self
end
end
items.numeric_sort_by(:product, :sku)
Is it all makes any sense?
Can you please point me in the right direction to implement the syntax I mentioned above, if it is possible at all.
Thanks.
EDIT: the sku could be alphanumeric as well. Sorry for the confusion.
Try this solution.
There is no error handling.
It's just an idea to develop if you like it.
class Array
def numeric_sort_by(*args)
self.sort_by do |element|
object = element
args.size.times { |n| object = object.send(args[n]) }
object.to_f
end
end
end
items.numeric_sort_by 'product', 'sku'
So the straightforward implementation was:
sorted = items.sort_by { |item| Integer(item.product.sku) } rescue items
And the desired was:
items.numeric_sort_by { |item| item.product.sku }
I was manage to achieve it by yielding a block into the sort_by:
class Array
def numeric_sort_by(&block)
return to_enum :numeric_sort_by unless block_given?
self.sort_by { |element| Integer(yield(element)) } rescue self
end
end

How to trasform all values in a nested hash?

I want to convert all the values in a nested hash to a utf8 compatible string. I initially thought this would be easy and something like deep_apply should be available for me to use, but I am unable to find anything this simple on a quick google and SO search.
I do not want to write (maintain) a method similar to the lines of Change values in a nested hash . Is there a native API implementation or a shorthand available for this or do I have to write my own method?
I ended up implementing my own approach, that is in no way perfect but works well for my use case and should be easy to maintain. Posting it here for reference to anyone who wants to try it out
def deep_apply object, klasses, &blk
if object.is_a? Array
object.map { |obj_ele| deep_apply(obj_ele, klasses, &blk) }
elsif object.is_a? Hash
object.update(object) {|_, value| deep_apply(value, klasses, &blk) }
elsif klasses.any? { |klass| object.is_a? klass }
blk.call(object)
else
object
end
end
usage:
=> pry(main)> deep_apply({a: [1, 2, "sadsad"]}, [String, Integer]) { |v| v.to_s + "asd" }
=> {:a=>["1asd", "2asd", "sadsadasd"]}
Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.
First, the helper methods:
From the top answer here (converting-a-nested-hash-into-a-flat-hash):
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)
class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end
And then a method tying it together:
def change_all_values(hash, &blk)
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))
flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end
A usage example:
irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}
There is deep_merge
yourhash.deep_merge(yourhash) {|_,_,v| v.to_s}
Merge the hash with itself, inspect the value and call to_s on it.
This method requires require 'active_support/core_ext/hash' at the top of file if you are not using ruby on rails.
Obviously, you may handle the conversion of v inside the deep_merge as you like to meet your requirements.
In rails console:
2.3.0 :001 > h1 = { a: true, b: { c: [1, 2, 3] } }
=> {:a=>true, :b=>{:c=>[1, 2, 3]}}
2.3.0 :002 > h1.deep_merge(h1) { |_,_,v| v.to_s}
=> {:a=>"true", :b=>{:c=>"[1, 2, 3]"}}
Well, it's quite simple to write it - so why don't write your own and be absolutely sure how does it behave in all situations ;)
def to_utf8(h)
if h.is_a? String
return h.force_encoding('utf-8')
elsif h.is_a? Symbol
return h.to_s.force_encoding('utf-8').to_sym
elsif h.is_a? Numeric
return h
elsif h.is_a? Array
return h.map { |e| to_utf8(e) }.to_s
else
return h.to_s.force_encoding('utf-8')
end
return hash.to_a.map { |e| result.push(to_utf8(e[0], e[1])) }.to_h
end
You may want to check if all behavior and conversions are correct - and change it if necessary.

How do I convert an array of hashes into a sorted hash?

If I have an array of hashes, each with a day key:
[
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
What is the best way to convert it to a hash with sorted day values as the keys:
{
:1=>[{:day=>1,:name=>'Ben'}],
:4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
I'm using Ruby 1.9.2 and Rails 3.1.1
Personally, I wouldn't bother "sorting" the keys (which amounts to ordering-by-entry-time in Ruby 1.9) until I actually needed to. Then you can use group_by:
arr = [{:day=>4,:name=>'Jay'}, {:day=>1,:name=>'Ben'}, {:day=>4,:name=>'Jill'}]
arr.group_by { |a| a[:day] }
=> {4=>[{:day=>4, :name=>"Jay"}, {:day=>4, :name=>"Jill"}],
1=>[{:day=>1, :name=>"Ben"}]}
Instead, sort the keys when you actually need them.
Assuming you array is called is list, here's one way using the reduce method:
list.reduce({}) { |hash, item|
(hash[item[:day]] ||= []) << item; hash
}
Here's another using the map method, but you have to carry a holder variable around:
hash = {}
list.each { |item|
(hash[item[:day]] ||= []) << item
}
Once you have the unsorted hash say in variable foo, you can sort it as,
Hash[foo.sort]
Simple answer:
data = [
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
#expected solution
sol = {
1=>[{:day=>1,:name=>'Ben'}],
4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
res = {}
data.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
Problem with this solution: The hash is not sorted as requested. (Same problem has Anurags solution).
So you must modify the answer a bit:
res = {}
data.sort_by{|h| h[:day]}.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
In Rails you can use OrderedHash:
ActiveSupport::OrderedHash[arr.group_by { |a| a[:day] }.sort_by(&:first)]
Update: In fact in Ruby 1.9 hash is ordered, so using ActiveSupport extension is not required:
Hash[arr.group_by { |a| a[:day] }.sort_by(&:first)]

Resources