Note Array Differences - Ruby - ruby-on-rails

I have two arrays. One is a set of skills, the other is a set of skills that a user can do. To illustrate :
['swimming', 'rowing', 'cycling'] is the set of all skills
Now, a user can have its set of skills like :
['rowing', 'cycling']
How can i create a nice hash that will present whether a user has a current skill ? Like for this particular user, it would be :
{'swimming' => no, 'rowing' => yes, 'cycling', => yes}
P.S. I actually want to do this with active record objects in rails, but i suppose it's the same idea.

The following will give true and false instead of yes and no.
all_skills = %w[swimming rowing cycling]
user_skills = %w[rowing cycling]
Hash[all_skills.map{|k| [k, user_skills.include?(k)]}]
or, if you don't mind getting nil instead for the no cases, the following is faster.
Hash[user_skills.map{|k| [k, true]}]}

Here's one terse way to do that:
ALL_SPORTS = ['swimming', 'rowing', 'cycling']
user_array = ['rowing', 'cycling']
user_hash = ALL_SPORTS.inject(Hash.new) { |h, sport| {sport => user_array.include?(sport)}.merge(h) }

I am presuming when you say 'yes' and 'no' you really want boolean values. The following doesn't use any intermediate values and only relies on the user's defined sports:
> h = Hash.new(false).merge(Hash[%w[rowing swimming].map {|v| [v.to_sym, true]}])
=> {:rowing=>true, :swimming=>true}
Now if you invoke any other sport as a key that the user doesn't have you get the desired result:
> h[:golf]
=> false
This also assumes you want symbols for keys too.

Related

Check if a cell in an ActiveRecord Array have been changed

I'm using PostgreSQL which supports arrays, which is very convenient. Sometimes I need to see if an element have been changed.
I know we can do this:
MyActiveRecordArray.changed?
=> true
But is there a way do this:
MyActiveRecordArray[0].changed?
Thank you for your answers and help!
Although you can't do it directly, you may use the changes method:
model = Model.new
model.array_field = [1]
model.changed?
=> true
model.changes['array_field']
=> [[], [1]]
doc: https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changes
Here is the solution I come up with, thanks to your answer
changes_index = []
old_array, new_array = model.changes['array_field']
new_array.each_with_index { |x, i| changes_index << i if x != old_array[i] }
You can ask directly your ActiveRecord instance if a specific attribute has changed by using attribute_name_changed? method.
On your case:
MyModel.active_record_array_changed?
This would return true or false depending whether the specific attribute has changed.

Sorting using custom value system in ruby

I have an application that dynamically creates a drop-down menu based on certain values in the database. Often the drop-down values are just in the order they come up but I would like to put them in a certain order.
An example of my value system:
Newbie = 0
Amateur = 1
Skilled = 2
Pro = 3
GrandMaster = 4
How would I take the data above and use it to sort an array full of those values (Newbie etc). I've thought about creating a hash of the values but even then I still am not sure how to apply that to the sort method.
Any help would be appreciated.
You can sort this array just by using the usual sorting the sorting won't be done by name it will be done by value. and if these are not integer objects and are some user defined class then sorting based on a particular attribute can be achieved very efficiently by
lst.sort_by &:first
where first is the attribute of the object.
Sort has by value:
hash = {:Newbie=>0, :Amateur=>1, :Skilled=>2, :Pro=>3}
> hash.sort { hash{a} <=> hash{b} }
=> [[:Newbie, 0], [:Amateur, 1], [:Skilled, 2], [:Pro, 3]]
Or use Ruby Hash#sort_by method:
hash.sort_by { |k,v| v }
Suppose you have a Level model that has a sort_id identifying the displayed order and a name holding the displayed name. I recommend using default_scope to set the default order for that model because it is likely that you always want to sort Level records this way:
class Level < ActiveRecord::Base
#### attributes
# id (integer)
# name (string)
# sort_id (integer)
default_scope order('sort_id ASC')
# rest of model...
end
Then, the only thing you have to do in your view to display a picklist is
<%= f.select("level", Level.pluck(:name)) %>
An alternate to #padde. I prefer to avoid default scopes.
class Level < ActiveRecord::Base
#### attributes
# id (integer)
# name (string)
# value (integer)
end
In the view
<%= f.select("level", Level.order(:value).map{|l| [l.name, l.value] } %>
Due to my poorly explained question the others trying to answer my question didn't really get a chance but using their help I did manage to figure out my problem.
ex_array = ["GrandMaster", "Newbie", "Pro", "Skilled", "Amateur"]
value_sys = {:Newbie=>0, :Amateur=>1, :Skilled=>2, :Pro=>3, :GrandMaster=>4}
ex_array.sort { |a,b| value_sys[a.to_sym] <=> value_sys[b.to_sym]
=> ["Newbie", "Amateur", "Skilled","Pro", "GrandMaster"]
Thanks for the help guys. Much appreciated.
Parse your value system for further use:
values = <<EOF
Newbie = 0
Amateur = 1
Skilled = 2
Pro = 3
GrandMaster = 4
EOF
value_map = Hash[values.split("\n").map{|v| v.split(/\s*=\s*/)}.map{|v| [v[0], v[1].to_i]}]
#=> {"Newbie"=>0, "Amateur"=>1, "Skilled"=>2, "Pro"=>3, "GrandMaster"=>4}
Assign the array values a weight according to value_map to transform the array into a new one, sort according to the weight, and then transform the new array back.
# here I created a sample array
array = value_map.keys.shuffle
#=> ["Newbie", "Pro", "Skilled", "Amateur", "GrandMaster"]
# transform and sort
sorted = array.map{|v| [v, value_map[v] || 0xFFFF]}.sort_by{|v| v[1]}.map{|v| v[0]}
#=> ["Newbie", "Amateur", "Skilled", "Pro", "GrandMaster"]
Or you can bypass the transform step and just use the sort_by method:
sorted = array.sort_by{|v| value_map[v] || 0xFFFF}

Using best_in_place gem how do I specify a nil value for a select tag?

The answer on this question has provided me with a nice roadmap for how to generate select tags with data from a collection on an association.
This works nicely and everything is going great.
The issue I have now is, how do I handle an empty collection?
With the regular :type => :input, I can just specify :nil => "Some nil message here".
But that doesn't seem to work for the collection, and to make matters worse, when there is nothing in the collection it seems to be displaying some integers (i.e. 1 and 2). I am assuming those are the IDs from the previously displayed objects in the collection, but for obvious reasons that doesn't make much sense.
Any ideas on how I can handle an empty collection with this gem?
Thanks.
Edit 1:
One alternative is to just put my original best_in_place helper tag inside an if statement for when a collection is not nil. But then how does the user edit it when it is blank? Perhaps there may be no way to handle this, because it would involve creating a new record in the collection.
I use a "workaround" for the empty options in a select tag, it could help you:
:type => :select, :collection => #my_colletion || [[I18n.t('common.none'), -1]]
When #my_colletion is nil, it shows a choice named 'None' with id = -1 (wich is not that bad to handle in the backend).
This part of code assumes the #my_collection is an array of arrays like [ [key, value], [key, value], ... ] OR nil.
Although, if you want your MyModel.all collection to fit the conditions for best_in_place, you can use the following:
#my_collection = MyModel.all.map{ |object| [object.name, object.value] }
# => this returns an array like [ [key, value], [key, value], ... ]
# => returns an empty array (NOT nil) if there is no entry in the DB
About the -1 id:
Using the -1 id as 'none' is easy because you don't need to explicitly handle the value nil (tests, etc). With the -1 id, you can use the following:
MyModel.where(id: params[:id]).first # => Returns the first object that has the params[:id]
# if params[:id] is -1, it will return nil and not raise an error.
I hope it helped :)

how to work with array of hashes

[[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
is the array
how can I get the value corresponding to particular value.
say High returns 5 in this.
or how to convert this array of hashes to an array so that searching becomes easy.
I tried:
find_all { |v| v['name'] == "Low" }
but it says:
cant convert String to Integer
please provide some guidance
How about making a single hash out of it for efficient querying?
arr.flatten.reduce(:merge)
#=> {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
If you have some code like:
array = [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
Then turn it into an ruby hash:
hash = array.inject({}) {|h, e| h.merge(e.first) }
# => {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
So you can find 'Low' value easily :
hash['Low']
# => 3
EDIT: The answer of Mark Thomas is pretty great, and shorter than the inject since it does the same thing. He wrote it before I answered. Nice ;)
In the general case, the hashes won't be unique, so you need to filter rather than pick one via indexing. For example, let's say you have this:
arr = [[{:apple => 'abc'}], [{:banana => 'def'}], [{:coconut => 'ghi'}]]
# => [[{:apple=>"abc"}], [{:banana=>"def"}], [{:coconut=>"ghi"}]]
Now let's suppose you want to get the value corresponding to any hash with a :coconut key. Then just use:
arr.flatten.map { |h| h[:coconut] }.compact
# => ["ghi"]
That gives you the list of answers. In this case there's only one matching key, so there's only one entry in the array. If there were other hashes that had a :coconut key in there, then you'd have something like:
# => ["ghi", "jkl", "mno"]
On the whole, though, that's a very unusual data structure to have. If you control the structure, then you should consider using objects that can return you sensible answers in the manner that you'd like, not hashes.
You could simply call #flatten on the original array. That would give you an array of hashes. What I think you would really want is just one hash.
1.8.7 :006 > [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]].flatten
=> [{"Postponed"=>10}, {"Low"=>3}, {"Medium"=>4}, {"High"=>5}]
I would ask, what are you doing to get that original structure? Can that be changed?
How about this?
arr = [
[{"Postponed"=>10}],
[{"Low"=>3}],
[{"Medium"=>4}],
[{"High"=>5}]
]
arr1 = []
arr.each{|a|
arr1.push(a[0])
}
Although I wonder if you really just want to get one hash, which you'd do like so:
myHash = {}
arr.each{|a|
a[0].each{|b, c|
myHash[b] = c
}
}
You would then access it like myHash["Postponed"]

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources