I can do this in plain ruby
[3,2,1].each.with_index do |e, i|
p e, i
end
3
0
2
1
1
2
But I can't do this with Mongoid:
Model.each.with_index do |e, i|
p e, i
end
It fails with
undefined method with_index for Array
How can I fix this without using this:
Model.each_with_index
Which does not allow starting index to be set
In Mongoid 3.1.3, with_index method works as expected.
puts Mongoid::VERSION
class User
include Mongoid::Document
field :name, type: String
end
User.create([
{ name: 'Harai' },
{ name: 'Sasaki' }
])
User.each.with_index do |u, i|
puts "#{u.name}, #{i}"
end
The above code works like this:
$ ruby main.rb
3.1.3
Harai, 0
Sasaki, 1
Your problem might be because you are using older version of Mongoid.
Related
I have a yaml file in the format:
parameters:
- param_name: age
requires:
- name
- param_name: height
requires:
- name
Based on this format I would like to accept a hash of keys and values and determine if the combination of keys and values is valid. For example based on the above example if someone submitted a hash with the values:
{'age' => 15, 'height' => '6ft'}
it would be considered invalid since the parameter name is required. So a valid submission would look like
{'age' => 15, 'height' => '6ft', 'name' => 'Abe Lincoln'}.
Essentially what I want is this:
For each parameter object, if it has a requires array underneath it. Check all parameter param_names for elements in that array, if any are missing exit.
I have a very ugly double loop that checks for this but I want to tighten the code up. I think I can use blocks in order to validate the data I need. Here is what I have come up with so far:
require 'yaml'
requirements = YAML.load_file('./require.yaml')
require_fields = Array.new
requirements['parameters'].each do |param|
require_fields.concat(param['require']) if param.has_key? 'require'
end
require_fields.each do |requirement|
found = false
requirements['parameters'].each do |param|
if param['param_name'] == requirement
found = true
end
end
abort "#{requirement} is a required field" unless found
end
You can clean this up a lot if you make it more idiomatic Ruby:
require 'yaml'
requirements = YAML.load_file('./require.yaml')
require_fields = requirements['parameters'].select do |param|
param.has_key?('require')
end.map do |param|
param['require']
end
require_fields.each do |requirement|
found = requirements['parameters'].any? do |param|
param['param_name'] == requirement
end
abort "#{requirement} is a required field" unless found
end
You could also do this:
require_fields = requirements['parameters'].map do |param|
param['require']
end.compact
Where that's probably good enough so long as your require is either something or nil.
You could also transform that input YAML into a simple hash structure of dependencies:
dependencies = requirements.map do ||
[ param['param_name'], param['requires'] ]
end.to_h
Then you can test really easily:
dependencies.each do |name, requirements|
found = requirements.find do |required_name|
!dependencies[required_name]
end
abort "#{found} is a required field" unless found
end
This is a really rough adaptation of your code, but I hope it gives you some ideas.
I would go with subsequent checks, collecting errors and reporting all at once:
req = YAML.load 'parameters:
- param_name: age
requires:
- name
- param_name: height
requires:
- name'
input = {'age' => 15, 'height' => '6ft'}
req['parameters'].each_with_object([]) do |req, err|
next unless input[req['param_name']] # nothing to check
missed = req['requires'].reject { |param| input[param] }
errors = missed.map do |param|
[req['param_name'], param].join(' requires ')
end
err.concat(errors)
end
#⇒ ["age requires name", "height requires name"]
Or, chaining:
req['parameters'].each_with_object(Hash.new { |h, k| h[k] = [] }) do |req, err|
next unless input[req['param_name']] # nothing to check
req['requires'].each do |param|
err[param] << req['param_name'] unless input[param]
end
end.map do |missing, required|
"Missing #{missing} parameter, required for: [#{required.join(', ')}]"
end.join(',')
#⇒ "Missing name parameter, required for: [age, height]"
I start with an empty array, and a Hash of key, values.
I would like to iterate over the Hash and compare it against the empty array. If the value for each k,v pair doesn't already exist in the array, I would like to create an object with that value and then access an object method to append the key to an array inside the object.
This is my code
class Test
def initialize(name)
#name = name
#values = []
end
attr_accessor :name
def values=(value)
#values << value
end
def add(value)
#values.push(value)
end
end
l = []
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z', 'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do |key, value|
l.any? do |a|
if a.name == value
a.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
end
p l
I would expect to see this:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
Instead I just get an empty array.
I think that the condition if a.name == value is not being met and then the add method isn't being called.
#Cyzanfar gave me a clue as to what to look for, and I found the answer here
https://stackoverflow.com/a/34904864/5006720
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
#ARL you're almost there! The last thing you need to consider is when found actually returns an object since detect will find a matching one at some point.
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
You actually only want to add a new instance of Test when found return nil. This code should yield your desired output:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
I observe two things in your code :
def values=(value)
#values << value
def add(value)
#values.push(value)
two methods do the same thing, pushing a value, as << is a kind of syntactic sugar meaning push
you have changed the meaning of values=, which is usually reserved for a setter method, equivalent to attire_writer :values.
Just to illustrate that there are many ways to do things in Ruby, I propose the following :
class Test
def initialize(name, value)
#name = name
#values = [value]
end
def add(value)
#values << value
end
end
h_cluster = {} # intermediate hash whose key is the cluster name
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z',
'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do | server, cluster |
puts "server=#{server}, cluster=#{cluster}"
cluster_found = h_cluster[cluster] # does the key exist ? => nil or Test
# instance with servers list
puts "cluster_found=#{cluster_found.inspect}"
if cluster_found
then # add server to existing cluster
cluster_found.add(server)
else # create a new cluster
h_cluster[cluster] = Test.new(cluster, server)
end
end
p h_cluster.collect { | cluster, servers | servers }
Execution :
$ ruby -w t.rb
server=server_1, cluster=cluster_x
cluster_found=nil
server=server_2, cluster=cluster_y
cluster_found=nil
server=server_3, cluster=cluster_z
cluster_found=nil
server=server_4, cluster=cluster_x
cluster_found=#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1]>
server=server_5, cluster=cluster_y
cluster_found=#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2]>
[#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1, :server_4]>,
#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2, :server_5]>,
#<Test:0x007fa7a619aac8 #name="cluster_z", #values=[:server_3]>]
I have a 2D array:
a = [["john doe", "01/03/2017", "01/04/2017", "event"], ["jane doe", "01/05/2017", "01/06/2017", "event"]...]
I would like to convert it to a value object in ruby. I found how to do it with a hash Ruby / Replace value in array of hash in the second answer of this question but not a 2D array. I would like to assign the value at a[0][0] to an attribute named "name", a[0][1] to "date1", a[0][2] to "date2" and a[0][3] to "event".
This is something like what I'd like to accomplish although it is not complete and I dont know how to assign multiple indexes to the different attributes in one loop:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#I would like this loop to contain all 4 attr assignments
arr.each {|i| instance_variable_set(:name, i[0])}
This should be short and clean enough, without unneeded metaprogramming :
data = [["john doe", "01/03/2017", "01/04/2017", "event"],
["jane doe", "01/05/2017", "01/06/2017", "event"]]
class ScheduleInfo
attr_reader :name, :date1, :date2, :type
def initialize(*params)
#name, #date1, #date2, #type = params
end
def to_s
format('%s for %s between %s and %s', type, name, date1, date2)
end
end
p info = ScheduleInfo.new('jane', '31/03/2017', '01/04/2017', 'party')
# #<ScheduleInfo:0x00000000d854a0 #name="jane", #date1="31/03/2017", #date2="01/04/2017", #type="party">
puts info.name
# "jane"
schedule_infos = data.map{ |params| ScheduleInfo.new(*params) }
puts schedule_infos
# event for john doe between 01/03/2017 and 01/04/2017
# event for jane doe between 01/05/2017 and 01/06/2017
You can't store the key value pairs in array index. Either you need to just remember that first index of array is gonna have "name" and assign a[0][0] = "foo" or just use array of hashes for the key value functionality you want to have
2.3.0 :006 > a = []
=> []
2.3.0 :007 > hash1 = {name: "hash1name", date: "hash1date", event: "hash1event"}
=> {:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}
2.3.0 :008 > a << hash1
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}]
2.3.0 :009 > hash2 = {name: "hash2name", date: "hash2date", event: "hash2event"}
=> {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}
2.3.0 :010 > a << hash2
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}, {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}]
It sounds like you want to call the attribute accessor method that corresponds to each array value. You use send to call methods programmatically. So you need an array of the method names that corresponds to the values you have in your given array. Now, assuming the class with your attributes is called Data.
attrs = [:name, :date1, :date2, :event]
result = a.map do |e|
d = Data.new
e.each.with_index do |v, i|
d.send(attrs[i], v)
end
d
end
The value result is an array of Data objects populated from your given array.
Of course, if you control the definition of your Data object, the best things would be to give it an initialize method that takes an array of values.
Try this:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#name = []
#date1 = []
#date2 = []
#event = []
arr.each |i| do
name << i[0]
date1 << i[1]
date2 << i[2]
event << i[3]
end
end
end
#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
Very simply: making a program to track membership. In the above hash you can see the four membership status keys each with an array containing names.
I'm trying to create a find_member method which allows the user to enter a name and then searches each array for the name and tells the user which key the name was found in.
I'm not very good with hashes and in attempting to do this I've created a mess of loops and I imagine there's a very easy solution, I just haven't found it so far. Is there a really simple way to do this?
I've tried a few things and don't have all my past efforts still, but this is the latest mess I've ended up with, which is probably worse than what I had previously:
def find_member
puts "==Find Member=="
puts "Name: "
#name = gets.chomp
#members.each do |key|
key.values.each do |array|
array.each do |element|
if #name == element
puts "#{#name} found in #{key}"
else
puts "#{#name} not found in #{key}"
end
end
end
end
end
Thanks.
The most efficient way to do this is to create a one-to-many mapping of names to keys, and update that mapping only when #members changes.
def find_member(name)
update_names_to_keys_if_necessary
#member_to_keys[name]
end
def update_names_to_keys_if_necessary
new_hashcode = #members.hash
return if #old_members.hashcode == new_hashcode
#member_to_keys = #members.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h|
v.each { |name| h[name] << k } }
#old_members_hashcode = new_hashcode
end
Note that #old_members_hashcode evaluates to nil the first time update_names_to_keys_if_necessary is called, so #member_to_keys will be created at that time.
Initially we obtain
#member_to_keys
#=> {"Jill"=>[:approved], "Daniel"=>[:unapproved, :banned],
# "John"=>[:removed], "Jane"=>[:banned]}
Try it.
find_member("Jill")
#=> [:approved]
find_member("Daniel")
#=> [:unapproved, :banned]
find_member("John")
#=> [:removed]
find_member("Jane")
#=> [:banned]
find_member("Billy-Bob")
#=> []
You can use this itteration with include? method.
#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
def find_member_group(name)
#members.each { |group, names| return group if names.include?(name) }
nil
end
#name = 'Jane'
group_name = find_member_group(#name)
puts group_name ? "#{#name} found in #{group_name}." : "#{#name} not found."
# => Jane found in banned.
Hash#select is the method to use here:
def find_member(name)
#members.select {|k,v| v.include? name }.keys
end
find_member("Jill") #=> [:approved]
find_member("Daniel") #=> [:unapproved, :banned]
find_member("John") #=> [:removed]
find_member("Jane") #=> [:banned]
Explanation:
select as the name suggests selects and maps only those elements that satisfy the condition in the corresponding code-block. The code-block negates the need for an if statement. Within the code-block we check each key-value pair and if its value includes the name argument, then that key-value pair is selected and mapped to the final output. Finally we're only interested in the memberships (namely the keys), so we apply the keys method to get these in the form of an array.
I writing a little program to generate some bogus top-ten sales numbers for book sales. I'm trying to do this in as compact a fashion as possible and do it without using MySQL or another DB.
I have written out what I want to happen. I've created a bogus catalog array and a bogus sales array corresponding sales to the index of the catalog entries. That part all works great.
I want to create a third array that includes all the titles from the catalog array with the sales numbers from the sales array, like a join in a DB, but without any DB. I can't figure out how to do that part of it though. I think once I have it in there I can sort it the way I want it, but making that third array is killing. I cannot figure out what I'm doing wrong or how to do it right.
So given the following code:
require 'random_word'
class BestOnline
def initialize
#catalog = Array.new
#sales = Array.new
#topten = Array.new
inventory = rand(50) + 10
days = rand(1..50)
now = Time.now
yesterday = now - 86400
saleshistory = now - (days * 86400)
(1..inventory).each do
#catalog << {
:title => "#{RandomWord.adjs.next.capitalize} #{RandomWord.nouns.next.capitalize}",
:price => rand(5.99..29.99).round(2)}
end
(0..days).each do
#sales << {
:id => rand(0..#catalog.count),
:salescount => rand(0..24),
:date => rand(saleshistory..now) }
end
end
def bestsellers
#sales.each do
# THIS DOESNT WORK AND I'M STUCK AS HOW TO FIX IT.
# #topten << {
# :title => #catalog[:id],
# :salescount => #sales[:salescount]
# }
end
puts #topten.group_by{ |tt| tt[:salescount]}.sort_by{ |k,v| -k}.first(10)
end
end
BestOnline.new.bestsellers
How can I create a third array that contains the titles and number of sales and output the result of the top-ten books sold?
Try this out:
def bestsellers
#sales.each do |sale|
#topten << {
title: #catalog[sale[:id]][:title],
salescount: sale[:salescount] }
end
#topten.sort! { |x, y| y[:salescount] <=> x[:salescount] }
puts #topten.first(10)
end
I suggest you write:
def bestsellers(sales)
sales.max_by(10) { |h| h[:salescount][:salescount]] }
end
puts bestsellers(sales)
Enumerable#max_by was permitted to have an argument in Ruby v2.2.
There are several problems with the way you've structured your code. Now that you have running code (by incorporating #fbonds66's answer), I suggest you post it at SO's sister-site Code Review. The purpose of CR is to suggest improvements to working code. If you read through some of the questions and answers there I think you will be impressed.
I was doing the dereferencing wrong trying to build the 3rd array of the 1st two:
#sales.each do |sale|
#topten << {
:title => #catalog[sale[:id]][:title],
:salescount => sale[:salescount]
}
end
I needed to work on the hash returned from .each as |sale| and use correct syntax to get what I was after from the other arrays.