Rails sum of one to many error - ruby-on-rails

I'm has books. Each book has list of chapters. Each chapter has text. I'm need calculate total value of characters. I'm wrote such code:
symbols = 0
b.chapters.all.each do |c|
symbols += c.text.length
end
And that's work fine. But, when i wrought:
symbols = b.chapters.all.sum(:text.length)
It's return invalid count of chars. Did anyone has any suggestion where i'm wrong?

You could write using the block verion of #sum :
b.chapters.sum { |c| c.text.length }
This is wrong : b.chapters.all.sum(:text.length)
Because - :text.length gives you the length of symbol :text as 4. And the 4 is summed up n times, where n is the size of the collection b.chapters.
I tried with the data as I have in my project :
[21] pry(main)> Menu.first.dishes.count # => 5
[22] pry(main)> Menu.first.dishes.map { |d| d.dish_type.size } # => [9, 7, 5, 7, 6]
[23] pry(main)> Menu.first.dishes.sum { |d| d.dish_type.size } # => 34
I have has_many association between Dish and Menu. Now see the below another thing, which made your fool :
Menu.first.dishes.sum(:a.size) # => 5

Related

Update Model with column objects

I do hope the title is correct. Using a Rails 5 project with PostgreSQL and Ruby 2.3.1.
I have enabled hstore in my app. I do not know the correct way to update a table column with an object data. Make sense?
# There will be only two arrays:
cars = ["honda", "bmw"]
rate = [1, 2]
cars.zip(rate).map do |c,r|
Foo.find(1).update_attributes(bar: {c => r})
end
# Foo.find(1).bar = {"bmw" => 2}
I expect:
# Foo.find(1).bar = {"honda" => "1", "bmw" => 2}
How to get the two values into bar?
I was trying to fit one of my previous questions into this but not sure where to start.
You can create a hash from the array
a = cars.zip(rate)
Foo.find(1).update_attributes(bar: Hash[a])
or if you need some enumerator
cars.zip(rate).each_slice(2) { |x| Foo.find(1).update_attributes(bar: Hash[x]) }

How can I use a regex match as a hash index in Ruby?

I'm new to Ruby and I've run into an issue I can't solve.
I'm trying to use gsub() to match a pattern in a string, then use that match as an index into a hash. So far, I haven't been able to figure it out. Here's some code:
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
assessment.gsub(/'(.+?)'/, '\1') # => "There are pig_num pigs on this farm"
assessment.gsub(/'(.+?)'/, farm) # => "There are pigs on this farm"
assessment.gsub(/'(.+?)'/, farm['\1']) # => TypeError: no implicit conversion of nil into String
assessment.gsub(/'(.+?)'/) { |key| farm[key] }
The first call to gsub() shows that I am matching the string I want.
The second call is an attempt to use the gsub(pattern, hash) flavor found at the Ruby documentation site.
The third call is trying to reference the value using the match as an index.
The fourth is some fancy pants way I thought might work using a lambda/proc/block.
What am I doing wrong?
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
1
"You may want to get the first object from farm hash but you need to tell from which hash you want to retrieve value". Otherwise, you need to use just Integer with String type like above.
assessment.gsub(/'(.+?)'/, '1')
2
when you 'gsub' string, you get 'pig_num' because you include '' inside the regex so that result would be "'pig_num'". However, the key of hash is "pig_num". "pig_num" and "'pig_num'" are different. That is why you can't get data properly.
assessment.gsub(/'(.+?)'/, farm)
3
You can not point index inside blacket but hash key
assessment.gsub(/'(.+?)'/, farm["pig_num"].to_s)
4
As I said before, you get "'pig_num'" as key. If you print out the key value inside the block, you will see it. You need to change it to 'Pure key'. To get rid of quotation, you can use gsub again and make it empty instead of quotation. gsub! is a destructive method which means modifies original value. If you use just gusb, the method returns modified value but the original key itself (in this situation) does not change. Otherwise, you need to assign the new value to another variable.
assessment.gsub(/'(.+?)'/) { |key| p key.gsub!("\'", ""); farm[key] }
I hope this answer is helpful for you. Cheers
Try this
assessment.gsub(/#{farm.keys.join('|')}/, farm)
I see on your code with regex match it will recognize 'pig_num' is the hash key to find on farm hash. So you need to change your hash like
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4} or you can change your regex. Example
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4}
assessment.gsub(/'(.+?)'/, farm) # => "There are 5 pigs on this farm"
Or
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment.gsub(/pig_num/, farm) # => "There are '5' pigs on this farm"
With some minor adjustments, your can use sprintf's %{name} or %<name>snotation:
farm = { pig_num: 5, horse_num: 2, cow_num: 4 }
assessment = "There are '%{pig_num}' pigs on this farm"
sprintf(assessment, farm)
#=> "There are '5' pigs on this farm"

i am trying to add a method each_run to the Ruby Array class which takes a code block expecting two arguments

Add a method each_run to the Ruby Array class which takes a code block expecting two arguments. It calls the code block once for each contiguous run of equal items in the array, sending the length of the run and the item repeated.the output will appear like this:
irb(main):001:0> load("eachrun.rb")
=> true
irb(main):002:0> [5,9,3,4,4,4,7,8,8].each_run { |n,x|
irb(main):003:1* print x, " appearing ", n, " times\n" }
5 appearing 1 times
9 appearing 1 times
3 appearing 1 times
4 appearing 3 times
7 appearing 1 times
8 appearing 2 times
=> nil
I would use group_by and yield the keys and size of the associated groups.
def each_run
group_by(&:itself).map {|k,v| yield v.length, k }
end
I wouldn't monkey-patch it directly into Array, though. First, it would make more sense as part of Enumerable, and second, monkey-patching can get messy. You could make it a refinement, though. Sadly, you can only refine classes, not modules, so it's back to Array instead of Enumerable:
module EachRun
refine Array do
def each_run
group_by(&:itself).map {|k,v| yield v.length, k }
end
end
end
Then just put using EachRun at the top of any code that you want to be able to use your new method.
I read the intention of the problem "It calls the code block once for each contiguous run of equal items in the array" as meaning the example list is a touch misleading since it contains no subsequent runs of a digit so that you can ignore the structure of the input list and just count how many times any given item appears. If the example had been:
[5,9,3,4,4,4,7,8,8,5,5]
Then printing "5 appears 3 times" doesn't suggest itself as correct.
I haven't written any Ruby in quite a while so this may not be optimal but it's what I came up with:
require 'test/unit/assertions'
include Test::Unit::Assertions
module Enumerable
def runs
if empty?
[]
else
runs = [[first,1]]
self[1..-1].each do |item|
if item == runs.last.first
runs = runs[0..-2] + [[item,runs.last.last+1]]
else
runs = runs + [[item,1]]
end
end
runs
end
end
def each_run(&block)
runs.each(&block)
end
end
assert_equal( [], [].runs )
assert_equal( [[5,1],[9,1],[3,1],[4,3],[7,1],[8,2],[5,2]], [5,9,3,4,4,4,7,8,8,5,5].runs )
[5,9,3,4,4,4,7,8,8,5,5].each_run do |m,n|
puts "#{m} appears #{n} times"
end
Outputting
> 5 appears 1 times
> 9 appears 1 times
> 3 appears 1 times
> 4 appears 3 times
> 7 appears 1 times
> 8 appears 2 times
> 5 appears 2 times
When I thought about it in Clojure what I came up with was this:
(defn runs [l]
(reduce (fn [memo [x n :as e]]
(let [[last-x last-n] (last memo)]
(if (= x last-x)
(conj (vec (drop-last memo)) [x (inc last-n)])
(conj memo e))))
[] (map (fn [e] [e 1]) l)))
(runs [5 5 9 8 1 1 4 3 3 3 3 5 5 5 2 2])
=> [[5 2] [9 1] [8 1] [1 2] [4 1] [3 4] [5 3] [2 2]]
This may give you a few ideas about how to proceed:
irb(main):001:0> a = [5,9,3,4,4,4,7,8,8]
=> [5, 9, 3, 4, 4, 4, 7, 8, 8]
irb(main):002:0> a.uniq.each { |e| printf("%d appears %d times\n", e, a.count(e)) }
5 appears 1 times
9 appears 1 times
3 appears 1 times
4 appears 3 times
7 appears 1 times
8 appears 2 times
I went with the suggest of using uniq and each but that's only if you need to return the uniq values as an array over all. There's lots of ways you can iterate it's up to you but the key here is monkey patching and yielding your values in order to use a block.
class Array
def each_run
uniq.each do |item|
yield(item, self.count(item))
end
end
end
[12] pry(main)> [5,9,3,4,4,4,7,8,8].each_run do |num, count|
[12] pry(main)* printf("%d appears %d times\n", num, count)
[12] pry(main)* end
5 appears 1 times
9 appears 1 times
3 appears 1 times
4 appears 3 times
7 appears 1 times
8 appears 2 times
=> [5, 9, 3, 4, 7, 8]
sources:
Monkey Patch Array Stack Overflow

How to get next values in hash

I have hash for example
{ 1 => 5, 3 => 6, 5 => 5, 8 => 10, 11 => 11}
and I have one key - 5, and I need get hash with next three key-value. in this case result will be:
{ 8 => 10, 11 => 11, 1 => 5}
How i can do this?
That is not a usual use case for a hash table. The whole point is to be able to look up specific keys efficiently, not iterate over keys in sequence.
If you want to do that, you'll need to choose another data structure, either instead of a hash or alongside the hash (if you still wish for efficient lookup).
If you know that they're integer keys, you could test for the existence of subsequent ones until you find three but that's pretty inefficient, especially if the current one is the second highest, for example. You would be better maintaining a different data structure.
as others have said you can't get the 'next' pair of values. If you're looking specifically for numerically ordered pairs where the keys are all numbers, you could do something like this:
h = { 1 => 5, 3 => 6, 5 => 5, 8 => 10, 11 => 11}
sorted_keys = h.keys.sort
sorted_keys.each do |key|
p "#{key} = #{h[key]}"
end
which returns:
"1 = 5"
"3 = 6"
"5 = 5"
"8 = 10"
"11 = 11"
You can't.
Ruby hashes are unordered. There's no reliable "next" key/value.

Using durations as hash keys in Rails

Since I'm dealing quite a lot with durations in my Rails app, I would also like to use them as hash keys in some places. However, it does not seem to work as expected for me.
Creating the initial hash works fine. For example, the following will work:
>> hash = {1.week => 'abc', 1.month => 'def'}
However, retrieving the values from the hash is not possible:
>> hash[1.month]
=> nil
Some further investigation showed me the reason for this:
>> 1.week.eql? 1.week
=> false
Which was quite unexpected
Furthermore, it seems like these objects behave differently to normal objects of FixNum class. For normal FixNum objects, the value of the object_id seems to be always the same, e.g:
>> 15.object_id
=> 31
>> 15.object_id
=> 31
For Durations, this is different, although they are from the same class
>> 1.month.class
=> Fixnum
>> 1.month.object_id
=> 70305513092140
>> 1.month.object_id
=> 70305513086860
So, it seems like the objects are always different, which is why hashes will not work. The only solution is to access with exactly the same object:
>> a = 1.month
=> 1 month
>> hash = {a => 'b'}
=> {1 month=>"b"}
>> hash[a]
=> "b"
Obviously, this is not always possible if you have a lot of objects with dynamically created durations.
Grouping by durations does not work either:
>> limits.group_by(&:duration)
=> #<OrderedHash {1 month=>[#<Limit:0x7fe28e441380>], 1 month=>[#<Limit:0x7fe28e4290c8>]}>
So, I'm wondering whether it is possible to get durations working as hash keys somehow? So far I have not found a good solution and I'm not sure if there is one. The important this is that functions like (Time.now - duration) should keep working.
FYI: My Ruby version - 1.8.7, Rails version - 2.3.18
Wow, that is a weird finding. This does work, and I haven't been able to break it, but it seems a little fragile:
hash = {1.year.inspect => "val1", 2.month.inspect => "val2", (1.year-4.days).inspect => "val3"}
=> {"1 year"=>"val1", "2 months"=>"val2", "1 year and -4 days"=>"val3"}
hash[2.months.inspect]
=> "val2"
hash[(1.year-4.days).inspect]
=> "val3"
and to get the durations back
hash.keys.collect{|k| eval(k.gsub(" and ","+").split(" ").join("."))}
=> [1 year, 2 months, 1 year and -4 days]

Resources