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}
Related
I am building an application in rails, and I have an items_controller which contains the methods application for create, show, edit, destroy etc.
However, I am trying to create my own method to access all the values in a specific column of my database and I am having greatly difficulty in capturing this data in an array.
I have tried the following ways of capturing the data (where 'quantity' is the column in the database for which I looking for):
#items = Item.find(params[:id])
#items2 = #item.find(params[:quantity])
I have also tried:
#items = Item.find(params[:quantity])
& even:
#items = Item.all
#items2 = #item.find(params[:quantity])
However, none of these methods appear to be working. For what I am doing it is not even essential to
know which quantity column values relate to which row...just getting a list of the column values would suffice.
If any one knows what is wrong here, the help you be very greatly appreciated!
Thanks.
UPDATE:
For clarity I am trying to retrieve all the data for a particular column in my database associated with the items_controller and filter the data for a particular piece of data (in this case the string "7" - as the data is returned from the db as a string when using the Items.all method.
I then want a counter to increase each time the "7" is encountered in the quantity column.
def chartItems
#items = Item.find(params[:id])
#items2 = #items.find(params[:quantity])
#filter = custom_filter_for(#items2)
def custom_filter_for(value)
j=0 # counter initialised at 0
value.each do |x|
if x == "7" # checking for data equal to "7" - the num is retrieved as a string
j = j+1 # increase j counter by 1 whenever "7" is encountered as a quantity
end
return j
end
end
Your find parameter is handled as an id in this case:
#items = Item.find(params[:quantity])
All items are returned which has the id of your quantity parameter. This is clearly not what you want.
You can select Items based on quantity:
#items = Item.find_by_quantity(params[:quantity])
But if you need only the quantities in an array, this is what you are looking for:
#quantities = Items.select(:quantity).map(&:quantity)
Your updated question:
result = Items.find_by_quantity(params[:quantity]).count
In new versions of ActiveRecord, they've added the pluck which does essentially what #Matzi's select and map method does.
To get all item quantities, you could do
#quantities = Item.pluck(:quantity)
Also, I would double check your use of the find_by helpers. I think that find_by_quantity will only give you a single match back (#item, not #items). To get all, I think you really want to use where
#quantities = Item.where(:quantity => params[:quantity])
If you were to use the pluck I mentioned above, I think your filtering step could also be written pretty concisely. That filter is simply counting the number of 7's in the list, right?
#quantities = Item.pluck(:quantity)
#filtered_values = #quantities.select{|q| q == 7}.length
I hope this helps out.
I'm building a custom profile completeness tool in Rails 3.1. Using active_admin, the administrator wants to be able to pick model attributes and assign a score value to them. I create a table with 'name' and 'score' in it. The name being the column name to apply the score to.
I need to compare the values from the name column in the scoring table when the model gets updated. Psuedocode:
ProfileScore.find(:all, :select => "name,score").inject { |p,score|
#this is where im stuck, 'self' == updated model hash
p[:name] == self.attribute.updated
score += p[:score]
}
Of course other approaches are welcome! I looked at completeness-fu but it is way out of date.
score = ProfileScore.select("name, score").inject(0) { |memo, pscore|
# the result is a list of ProfileScore objects, not of pairs.
# if p[:name] == self.attribute.updated # don't you want to take into account
# also old, but present, attributes??
if self.attributes[p[:name]].present?
memo += p[:score]
end
memo
}
or also
present_attributes = self.attributes.reject { |k,v| v.blank? }.keys
score = ProfileScore.where(:name => present_attributes).sum("score")
To get the total score, you can use:
total_score = ProfileScore.where(:name => self.attribute.updated).sum(:score)
[[{"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"]
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.
I am trying to do something rather simple I believe:
1) insert a value in an array field only if that value is not already present
2) remove a value if it exists in the array
I have just no idea how to do any of this things... for the moment I am just inserting my value without checking if it exists already: myArray << obj.id
Thanks,
Alex
ps: using Rails 3.0.3, mongo 1.1.5 and mongoid 2.0.0.rc5
ps2: this is the mongodb syntax to achieve what I want, but I have no idea how to do this in mongoid
{ $addToSet : { field : value } }
Adds value to the array only if its not in the array already, if field is an existing array, otherwise sets field to the array value if field is not present. If field is present but is not an array, an error condition is raised.
To add many valuest.update
{ $addToSet : { a : { $each : [ 3 , 5 , 6 ] } } }
$pop
{ $pop : { field : 1 } }
removes the last element in an array (ADDED in 1.1)
{ $pop : { field : -1 } }
removes the first element in an array (ADDED in 1.1) |
You want to use the add_to_set method, as documented (somewhat) here: http://mongoid.org/en/mongoid/docs/persistence.html#atomic
Example:
model = Model.new
model.add_to_set(:field, value)
model.save
You can give it a single value or even an array of values. The latter will use mongo's $each qualifier along with $addToSet when adding each element of your array to the field you specify.
As per Chris Hawk from Mongoid googlegroup:
Arrays in Mongoid documents are simple Ruby arrays. See the docs for
the Array class: http://www.ruby-doc.org/core/classes/Array.html
So, for insertion you can simply do:
array << object unless array.include?(object)
And for removal:
array.delete(object)
worth mentioning, in mongoid, as of 2.0.0pre4 I do not see any addToSet support. mongo_mapper (while imo less maintained :( ) supports this via push_uniq method.
short of that, in mongoid, if you are working with the relationship method directly, you don't need to do the include?. if you are working with the array directly, you do.
Example:
class Person
include Mongoid::Document
has_and_belongs_to_many :pets ## or has_many :pets, :stored_as => :array if your not using the latest mongoid
end
#when using .pets no need to check, mongoid checks for you to ensure you can only add the same pet once.
pet = Pet.find("294x29s9a9292")
p = Person.find("42192189a92191a")
p.pets << pet
#when using pet_ids, you do need to check:
pet = Pet.find("294x29s9a9292")
p = Person.find("42192189a92191a")
p.pet_ids << pet.id unless p.pet_ids.include? pet.id
You can use the pull operator, which is atomic, to remove a single element. Here's an example, where l is a reference to a document with an array field array:
l.array = [1, 2, 3]
l.save
l.pull(array: 1) # atomically removes 1, leaving [2, 3]
Adding can be done with add_to_set, as previously mentioned:
l.array = [2, 3]
l.save
l.add_to_set(array: 1) # atomically adds 1, resulting in [2, 3, 1]
Here's a link to the current Mongoid documentation on Atomic operators.