Merge like array values to new grouped array? - ruby-on-rails

I need to combine like array values as follows:
From:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
To:
merged_like_arr = [ ['abc', 'abc'], ['eff', 'eff'], ['foo'], ['bar', 'bar', 'bar']]
Basically have an ActiveRecord object which returns a collection of records in order.
aff=Registration.order('affiliate DESC')
aff.map{|code| code. affiliate }
# produces the following:
# arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
I need to be alter the data form arr to merged_like_arr (as shown above) so that I can do:
merged_like_arr.sort_by{|arr|-arr.size}.first(10)
#=> [ ["bar", "bar", "bar"], ["eff", "eff"], ["abc", "abc"] ... ]
The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
Alternative implementations are also welcome. Thank you!

The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
You can use count with group for that kind of query:
If count is used with group, it returns a Hash whose keys represent the aggregated column, and the values are the respective amounts:
Person.group(:city).count
# => { 'Rome' => 5, 'Paris' => 3 }
In your case:
Registration.group(:affiliate).count
#=> { 'abc' => 2, 'eff' => 2, 'foo' => 1, 'bar' => 3 }

This should work:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
arr = arr.group_by { |e| e }.values
# => [["abc", "abc"], ["eff", "eff"], ["foo"], ["bar", "bar", "bar"]]

Related

How to merge hash with activerecord relations in controller / scope

I have Item model(table) with column [id,name,notes]. then I have hash lets call it stock with column [id_of_item,total_stock],
when I do query in controller I would like to join the hash into the table as additional column so I can show the total_stock of the item.
I prefer not to use map/each (looping through all the items since the items table has thousand records. I still don't know whether this possibly or not, thank you.
if your stock is
[[1, "total_stock_1"], [2, "total_stock_2"]]
you should use
stock = Hash[[[1, "total_stock_1"], [2, "total_stock_2"]]]
to translate your hash to this style
stock = {1 => "total_stock_1", 2 => "total_stock_2"}
stock = {1 => "total_stock_1", 2 => "total_stock_2"}
#items = Item.all.map{|item| item.attributes.merge({total_stock: stock[item.id]})}
# the output will be a json not a ActiveRecordRelation
[
{:id => 1, :name => 'item1', :notes => xxx, :total_stock => "total_stock_1"},
{:id => 2, :name => 'item2', :notes => yyy, :total_stock => "total_stock_2"}
]
You can do this in controller:
#items = Item.all
render json: #items.map{|item| {'item': item.as_json.merge stock.select{|item| item['id_of_item'] == item.id}['total_stock']} }}

Looking for a rspec matcher that checks a value to be included in array

How can I write this:
Given an array b = ['one', 'two', 'three']
I expect value a to be
included in the array b.
I want to use it together with an all matcher so my final code would look like this:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
expect(my_list).to all(include("type" => a_value_included_in(b))
Which is testing for:
all hashes from my_list must have a type key whose value is in array b.
Is there such built-in matcher in Rspec?
And how do you check inclusion of a value in the array besides using the obvious reverse: expect([1, 2, 3]).to include(value), which in my example, is not really fitting in?
If you want to check if every string from b is present in any of the hashes from my_list:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
b.each do |str|
expect(my_list.find { |h| h['type'] == str }).to be_present
end
Or, you can go with:
expect(my_list.map { |h| h['type'] }.sort) to eq(b.sort)
So when someone adds my_list << { 'type' => 'not_in_list' } the spec
will fail.
expect(my_list.map { |h| b.include?(h['type']) }.uniq).to eq(true)
In your case I would simply use (order of elements doesn't matter):
expect(my_list).to match_array(b.map{|v| { 'type' => v })
or use include matcher instead of match_array if b can ba accepted as a subset of my_list.
I don't think that special matcher for such cases exists and/or it's really needed. Still, you can create a custom one if you really can't live w/o it.

Rspec: Check if array includes object which includes property

I have a json array full of objects.
my_array = [{id => 6, name => "bob"},
{id => 5, name => "jim"},
{id => 2, name => "steve"}]
I need to see if the array holds an object which contains an attribute "id" that is set to 5. The "name" attribute is unknown.
How do I do this in rspec?
I know if I had the name attribute I know I could just do:
my_array.should include({:id => 5, :name => "jim"})
expect(myArray.find { |item| item[:id] == 5 }).to_not be_nil
or with the legacy should syntax
myArray.find { |item| item[:id] == 5 }.should_not be_nil
Please note that myArray is not following Ruby conventions. Variables use underscore
my_array
not camelcase
myArray
It is also possible using the having_attributes alias:
expect(my_array).to include( an_object_having_attributes(id: 5) )
or, as in my own use case, matching the whole array:
expect(my_array).to contain_exactly(
an_object_having_attributes(id: 5),
an_object_having_attributes(id: 6),
an_object_having_attributes(id: 2)
)
You can unfold an array and to check matching of two arrays like here:
expect(my_array).to include(*compare_array)
It'll unfold and match each value of array.
It's equivalent to this:
expected([1, 3, 7]).to include(1,3,7)
Source: Relish documentation
This would only be worth it if you were doing many of these, but you could define a custom matcher:
RSpec::Matchers.define :object_with_id do |expected|
match do |actual|
actual[:id] == expected
end
description do
"an object with id '#{expected}'"
end
end
# ...
myArray.should include(object_with_id 5)
Put this any matcher into spec/support/matchers.rb and require it in your spec_helper.rb
RSpec::Matchers.define :any do |matcher|
match do |actual|
actual.any? do |item|
matcher.matches?(item)
end
end
end
Then you can use it in examples like this:
expect(my_array).to any(include(id: 5))
I would use the RSpec 3's composable include matcher like so:
expect(my_array).to include(include(id: 5))
This would have the benefit of a more verbose output via RSpec in case of failure.
it 'expects to have element with id 3' do
my_array = [
{ id: 6, name: "bob" },
{ id: 5, name: "jim" },
{ id: 2, name: "steve" }
]
expect(my_array).to include(include(id: 3))
end
This would generate following failure message:
Failures:
1) Test expects to have element with id
Failure/Error: expect(my_array).to include(include(id: 3))
expected [{:id => 6, :name => "bob"}, {:id => 5, :name => "jim"}, {:id => 2, :name => "steve"}] to include (include {:id => 3})
Diff:
## -1,2 +1,2 ##
-[(include {:id => 3})]
+[{:id=>6, :name=>"bob"}, {:id=>5, :name=>"jim"}, {:id=>2, :name=>"steve"}]
Further reading:
https://relishapp.com/rspec/rspec-expectations/docs/composing-matchers
Here's a customer matcher "include_object" (probably a better name should be used since it just checks if the id's are present)
used as follows
obj = {id:1}
objs = [{id: 1}, {id: 2}, {id: 3}]
expect(objs).to include_object obj
Matcher can handle Object, Hashs (symbols or string)
It also prints just the id's in the array on exception for easier viewing
RSpec::Matchers.define :include_object do |expected|
ids = []
match do |actual|
ids = actual.collect { |item| item['id'] || item[:id] || item.id }
ids.find { |id| id.to_s == expected.id.to_s }
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would contain the object with id '#{expected.id}'"
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would not contain the object with id '#{expected.id}'"
end
end
I see there are many responses, but here is an implementation that was helpful for me on a case which you need to check for multiple key-value pairs on each array object, instead of single attribute checking
Usage
expect(array).to include(have_attributes_with_values({ id: 5, name: 'Jim' }))
Matcher implementation
RSpec::Matchers.define :have_attributes_with_values do |expected|
match do |actual|
expected.each do |key, value|
return false unless actual[key] == value
end
end
end

How would I save multiple records at once in Rails?

How would I save this array in one call with Rails?
tax_rates = [{
:income_from => 0
:income_to => 18200
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => nil
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
}]
Can I just call Rails.create(tax_rates)?
Also, is there a way to remove duplicate symbols so they look neater?
Your example is almost correct.
Use ActiveRecord::Persistence#create, which can accept an array of hashes as a parameter.
tax_rates = [
{
income_from: 0,
income_to: 18200,
start: "01-07-2013",
finish: "30-06-2014",
rate: nil,
premium: nil,
},
{
income_from: 18201,
income_to: 37000,
start: "01-07-2013",
finish: "30-06-2014",
rate: 0.19,
premium: nil,
},
# ...
]
TaxRate.create(tax_rates) # Or `create!` to raise if validations fail
A nice solution is to use the active record import gem. I recommend it over now built-in Rails bulk insert because it's more flexible in the options in case of constraint violation.
TaxRate.import(
[:income_from, :income_to, :start, :finish, :rate, :premium],
tax_rates
)
Its definitely better than my old answer which would trigger a db commit per entry in the array :)
Old answer:
tax_rates.map {|tax_rate| TaxRate.new(tax_rate).save }
This way you'll retrieve an Array with true or false to know which did succeed and which didn't.
If you want all of them to be saved .or, non of them to be saved even if one fails, you can use 'ActiveRecord::Base.transaction'
e.g.
ActiveRecord::Base.transaction do
tax_rate.each do |tax_rt|
TaxRate.new(tax_rt).save
end
end
I am not sure about rails < 4.2 but I have tried it in rails 4.2 you can simply do this
TaxRate.create(tax_rt)
Here is an example like yours:
a = []
a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")
The array is saved all at once with:
a.each(&:save)
This will call B#save on each item in the array.
use a gem 'fast_inserter': https://github.com/joinhandshake/fast_inserter
it generates a single sql query of thousand records.
movie_data = [1, 'Climates (Iklimler)', 'Clay Pauwel', 'Drama'],
[2, 'Tinpis Run', 'Andros Glazer', 'Comedy'],
[3, 'Naked City, The', 'Bethena Chatband', 'Mystery'],
[4, 'Small Time Crooks', 'Naomi Plom', 'Crime'],
[5, 'Shadowboxer', 'Georgeanne Widdicombe', 'Thriller']
params = {
table: 'movies',
static_columns: {
created_at: '0000-00-00 00:00:00',
updated_at: '0000-00-00 00:00:00',
},
options: {
timestamps: false,
unique: true,
check_for_existing: true
},
group_size: 100,
variable_columns: %w(id title director description),
values: movie_data
}
inserter = FastInserter::Base.new(params)
inserter.fast_insert

How can I grab certain values in a hash in Ruby?

I have a hash
hash = { 1=> { 0=> 'apple', 1=> 'tree'... ....}, 2=> {.....}}
I want to grab the 0 for all hashes within the hash. I know there is a transpose for array, but there any way to do this with a hash easily?
Something like this should work:
hash.values.collect{|v| v[0]}
Example:
irb(main):001:0> hash = { 1 => { 0 => 'apple', 1 => 'tree' },
2 => { 0 => 'foo', 1 => 'bar' }}
=> {1=>{0=>"apple", 1=>"tree"}, 2=>{0=>"foo", 1=>"bar"}}
irb(main):002:0> hash.values.collect{|value| value[0]}
=> ["apple", "foo"]

Resources