So in my rails app I have a column of text boxes in pairs. There are 4 pairs of textboxes.
Just one of these pairs of text box needs to be filled out (with both textboxes in that pair filled out) for it to be valid.
So I'm trying to loop through them all and add them to a multi dimensional array and to check that at least one row(?) in the array has both values.
def no__values?
all_values = Array.new
txt_a_values = Array.new
txt_b_values = Array.new
self.item.line_items.each do |item|
txt_a_values << item.single.nil? # this is a value from the text box a
txt_b_values << item.aggregate.nil? # this is a value from the text box b
end
all_values << txt_a_values #create multi dimensional array
all_values << txt_b_values
all_values.each do |v|
??? # there needs to be one pair in the array which has both values
end
end
So it should create an array like this
[true][true] # both textboxes are nil
[false][false] # both textboxes have values
[true][true] # both textboxes are nil
[true][true] # both textboxes are nil
the above scenario is valid since there is one pair which BOTH have values
I really don't like the way I'm progressing with this, so I',m looking for some help.
Couldn't you do something like this:
def no__values?
self.item.line_items.each do |item|
return false if !item.single.nil? && !item.aggregate.nil?
end
true
end
That will return false when both values of a pair isn't null, and returns true if each pair had at least one null value.
Edit:
Based on the method name I didn't create the array and instead returns false/true directly.
I think you are looking for the following snippets to solve your problems. I have done it with little simplification. Please let me know if you are looking for this.
def no__values?
all_values = []
self.item.line_items.each do |item|
all_values << [item.single.nil?, item.aggregate.nil?]
end
all_values.each do |v|
puts v # [true, false] or [true, true], [false, false]....
end
end
Related
I have a Hash where the majority of it is filled with a key with two values associated with the key. There is also another hash within this Hash which is where I've been stuck.
Lets say the hash looks like:
{'sports'=>['football', 'basketball'], 'season'=>['summer','fall'], 'data'=>[{'holiday'=>'Christmas', 'genre' => 'Comedy'}, {'holiday'=>'Thanksgiving', 'genre' => 'Action'}]}
The output should look like:
Sports
- football
- basketball
Season
- summer
- fall
Holiday
- Christmas
- Thanksgiving
Genre
- Comedy
- Action
So far I have a helper that gives me everything except the data section.
def output_list_from(hash)
return if hash.empty?
content_tag(:ul) do
hash.map do |key, values|
content_tag(:li, key.to_s.humanize) +
content_tag(:ul) do
# if values.is_a?(Hash)...
content_tag(:li, values.first) +
content_tag(:li, values.last)
end
end.join.html_safe
end.html_safe
end
This returns the output:
Sports
- football
- basketball
Season
- summer
- fall
Data
- {'holiday'=>'Christmas', 'genre' => 'Comedy'}
- {'holiday'=>'Thanksgiving', 'genre' => 'Action'}
Which of course makes sense...so I've tried to check in the loop if the value is a Hash, but the way it's set up has tricked me. I think it's be easier if I knew what the hash would look like everytime, but it would be a new hash each time. One time there could be a holiday within data and the other time there could be both holiday and genre.
Any advice would be appreciated.
You will need to create a hash with the correct format. Something like this:
hash = {'sports'=>['football', 'basketball'], 'season'=>['summer','fall'], 'data'=>[{'holiday'=>'Christmas', 'genre' => 'Comedy'}, {'holiday'=>'Thanksgiving', 'genre' => 'Action'}]}
formatted_data = hash.dup
data = formatted_data.delete('data')
if data
data.each do |item|
item.each do |k, v|
formatted_data[k] ||= []
formatted_data[k] << v
end
end
end
puts formatted_data
# => {"sports"=>["football", "basketball"], "season"=>["summer", "fall"],
# => "holiday"=>["Christmas", "Thanksgiving"], "genre"=>["Comedy", "Action"]}
content_tag(:ul) do
formatted_data.map do |key, values|
#... your code here...
end.join.html_safe
end.html_safe
Suppose your hash looked like this:
hash = { 'sports'=>['football', 'basketball'],
'season'=>['summer', 'fall'],
'data1' =>[{ 'holiday'=>'Christmas', 'genre'=>'Comedy'},
{ 'holiday'=>'Thanksgiving', 'genre'=>'Action' }],
'data2' =>[{ 'data3'=>[{ 'sports'=>'darts', 'genre'=>'Occult' }] }]
}
and you wanted a general solution that would work for any number of levels and does not depend on the names of the keys that will not be in the resulting hash (here 'data1', 'data2' and 'data3'). Here's one way you could do that, using recursion.
Code
def extract(h, new_hash = {})
h.each do |k,v|
[*v].each do |e|
case e
when Hash then extract(e, new_hash)
else new_hash.update({ k=>[e] }) { |_,ov,nv| ov << nv.first }
end
end
end
new_hash
end
Example
extract(hash)
#=> {"sports"=>["football", "basketball", "darts"],
# "season"=>["summer", "fall"],
# "holiday"=>["Christmas", "Thanksgiving"],
# "genre"=>["Comedy", "Action", "Occult"]}
Explanation
There are, I think, mainly two things in the code that may require clarification.
#1
The first is the rather lonely and odd-looking expression:
[*v]
If v is an array, this returns v. If v is a literal, the splat operator has no effect, so it returns [v]. In other words, it leaves arrays alone and converts literals to an array containing one element, itself. Ergo:
[*['football', 'basketball']] #=> ["football", "basketball"]
[*'Thanksgiving'] #=> ["Thanksgiving"]
This saves us the trouble of having three, rather than two, possibilities in the case statement. We simply convert literals to arrays of one element, allowing us to deal with just hashes and arrays.
#2
The second snippet that may be unfamiliar to some is this:
new_hash.update({ k=>[e] }) { |_,ov,nv| ov << nv.first }
This uses the form of the method Hash#update (a.k.a. merge!) that uses a block to resolve the values of keys that are present in both hashes being merged. As an example, at some stage of the calculations, new_hash will have a key-value pair:
'sports'=>['football', 'basketball']
and is to be updated with the hash1:
{ 'sports'=>['darts'] }
Since both of these hashes have the key 'sport', the block is called upon as arbiter:
{ |k,ov,nv| ov << nv.first }
#=> { |'sport', ['football', 'basketball'], ['darts']| ov << nv.first }
#=> { |'sport', ['football', 'basketball'], ['darts']|
['football', 'basketball'] << 'darts' }
#=> ['football', 'basketball'] << 'darts'
As I'm not using the key 'sport' in the block, I've replaced that block variable with a placeholder (_) to reduce opportunities for error and also to inform the reader that the key is not being used.
1 I sometimes use darts as example of a sport because it is one of the few in which one can be successful without being extremely physically fit.
Sorry for the confusing title, not sure how to describe this issue.
Inside a Ruby on Rails controller I'm creating a list named #commits, where each item in #commits should contain a hash table whose elements are the values of various properties for each commit. These property values are stored in a Redis database.
Below, I iterate through a list of properties whose values should be grabbed from Redis, and then grab those values for each of 8 different commits. Then I place the values from redis into a different hash table for each commit, using the commit property name as the key for the hash.
# Initialize #commits as a list of eight empty hash tables
#commits = Array.new(8, {})
# Iterate over the attributes that need hashed for each item in #commits
[:username, :comment, :rev, :repo].each do |attrib|
# 8 items in #commits
8.times do |i|
# Get a value from redis and store it in #commits[i]'s hash table
#commits[i][attrib] = $redis.lindex(attrib, i)
# Print the value stored in the hash
# Outputs 7, 6, .., 0 for #commits[i][:rev]
puts #commits[i][attrib].to_s
end
end
# Print the value of every item that was stored in the hash tables above,
# but only for the :rev key
# Outputs 0 eight times
8.times do |i|
puts #commits[i][:rev]
end
However, per the comments above, #commits[0..7] all seem to have the same values in their hashes, despite them being seemingly stored correctly a few lines above. Using the hash key :rev as an example, the first puts outputs 7..0, which is correct, but the second puts outputs the number 0 eight times.
Anyone know why?
It would help if you show how #commits is initialized, but it looks like you've created a structure with multiple references to the same object.
Incorrect, same object recycled for all keys:
#commits = Hash.new([ ])
Correct, new object created for each key:
#commits = Hash.new { |h, k| h[k] = [ ] }
You could be using an Array with the same mistake:
#commits = Array.new(8, [ ])
This will lead to the following behaviour:
a = Array.new(4, [ ])
a[0]
# => []
a[0] << 'x'
# => ["x"]
a
# => [["x"], ["x"], ["x"], ["x"]]
It can be fixed by passing in a block:
a = Array.new(4) { [ ] }
a[0]
# => []
a[0] << 'x'
# => ["x"]
a
# => [["x"], [], [], []]
It is highly unusual to see an array pre-initialized with values, though. Normally these are just lazy-initialized, or a Hash is used in place of an Array.
I'm attempting to convert MySQL timestamps in an ActiveRecord object to another timestamp format. My method takes an array of ActiveRecord records and returns an array of hashes with the timestamped fields with the formatted timestamp:
def convert_mysql_timestamps(records)
ary = []
hash = {}
records.each_with_index do |record, i|
record.attributes.each do |field, value|
if time_columns.include?(field) and value then
hash[field] = value.strftime("%Y-%m-%dT%H:%M:%S%z")
else
hash[field] = value
end
end
ary[i] = {}
ary[i] = hash
end
ary
end
However, when in the ary[i] = hash assignment, all ary elements get set to hash.
Is there a better way to convert a record's timestamp fields? (I don't need to save the records back to the database.) Also, how can I get the array to capture each individual hash representation of the record?
Input:
[#<Vehicle id: 15001, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">, #<Vehicle id: 15002, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">]
Desired output:
[{"id"=>15001, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}, {"id"=>15002, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}]
The problem is that you're creating one Hash:
def convert_mysql_timestamps(records)
ary = []
hash = {}
#...
and then trying to re-use for each record. You probably want a fresh Hash for each each_with_index iteration:
def convert_mysql_timestamps(records)
ary = []
records.each_with_index do |record, i|
hash = { }
record.attributes.each do |field, value|
#...
end
ary[i] = hash
end
end
You can use map for this - it's always a good option when you want to take an array in one format and produce a same-sized array in another. Here's how:
def convert_mysql_timestamps(records)
records.map do |record|
Hash[records.attributes.map{|k, v| [k, convert_mysql_timestamp(v)] }]
end
end
def convert_mysql_timestamp(field, value)
return value unless time_columns.include?(field) && value
value.strftime("%Y-%m-%dT%H:%M:%S%z")
end
It works like so:
Hash[array_of_pairs] turns an array of key-value pairs - like [["foo", 2], ["bar", 3], ...] - into a hash like {"foo" => 2, "bar" => 3, ...}.
map calls its block for each item in the collection, and collects each return value of the block into a new array, which it returns. The attributes.map inside the Hash[...] creates the array of key-value pairs, and the outer records.map collects up all the hashes into the returned array.
I'd suggest reading up on the methods in Enumerable because there are so many neat things like map in there. You will find that you almost never have to use indices in your loops, although if you're coming from another language with for loops everywhere it's a hard habit to break!
I am not sure what your time_columns are, but assuming they are Time class, you can simplify the part like value.is_a?(Time).
def convert_mysql_timestamps(records)
records.collect do |record|
# assuming records are from a Rails model, I am using #attributes
# to loop through all fields in a record
# then inject values in this hash -> ({}),
# which is in the block, named attributes
record.attributes.inject({}) do |attributes, (column_name, value)|
# if it is Time, convert it to iso8601 (slightly different from your format,
# but if this is also acceptable, your code can be simpler)
attributes[column_name] = (value.is_a?(Time) ? value.iso8601 : value)
attributes
end
end
end
I have the following function that decodes a URL into a hash comprised of different pieces of the URL. For example, after the first line where I assign hash, I receive this output:
>> CGI.parse(URI.parse("http://google.com/?foo=bar&baz=hello").query)
=> {"foo"=>["bar"], "baz"=>["hello"]}
In this function, I'm trying to pull values if the key is "p" or "q". I essentially want to filter the hash into only the keys and values I want.
My goal is to only show the keywords someone searches for such as brick or cement.
Here's my function:
def self.get_search_terms(search_url)
hash = CGI.parse(URI.parse(URI.encode(search_url)).query) #returns a hash
keywords = []
hash.each do |key|
if key["q"] != nil ||
key["p"] != nil
keywords << key
end
end
keywords
end
One way to do it:
def self.get_search_terms(search_url)
hash = CGI.parse(URI.parse(URI.encode(search_url)).query)
keywords = []
hash.each do |key, value|
if key == 'p' || key == 'q'
keywords << value
end
end
keywords
end
The more idiomatic ruby way, essentially a one liner:
def self.get_search_terms(search_url)
CGI.parse(URI.parse(URI.encode(search_url)).query).values_at('q','p')
end
see Hash#values_at
If you want to eliminate empty params, add a reject blank to it:
.values_at('q','p').reject(&:blank?)
Probably one of 100 ways to do it!
def self.get_search_terms(search_url)
myhash = CGI.parse(URI.parse(URI.encode(search_url)).query)
keywords = []
["p","q"].each do |mykey|
if myhash.has_key?(mykey)
keywords << myhash[mykey]
end
end
keywords
end
Or maybe
def self.get_search_terms(search_url)
myhash = CGI.parse(URI.parse(URI.encode(search_url)).query)
keywords = []
mykeywords = ["p","q"]
mykeywords.each do |mykey|
if myhash.has_key?(mykey)
keywords << myhash[mykey]
end
end
keywords
end
In both versions, it's the list of keywords you are looking for that is the initial loop. You implicitly iterate over the hash extracted from the URI by using:
myhash.has_key?
That will iterate over my hash, and if it contains the key, ad the key's value to keywords.
Is there a way to get the actual columns name with ActiveRecord?
When I call find_by_sql or select_all with a join, if there are columns with the same name, the first one get overridden:
select locations.*, s3_images.* from locations left join s3_images on s3_images.imageable_id = locations.id and s3_images.imageable_type = 'Location' limit 1
In the example above, I get the following:
#<Location id: 22, name: ...
>
Where id is that of the last s3_image. select_rows is the only thing that worked as expected:
Model.connection.select_rows("SELECT id,name FROM users") => [["1","amy"],["2","bob"],["3","cam"]]
I need to get the field names for the rows above.
This post gets close to what I want but looks outdated (fetch_fields doesn't seem to exist anymore How do you get the rows and the columns in the result of a query with ActiveRecord? )
The ActiveRecord join method creates multiple objects. I'm trying to achieve the same result "includes" would return but with a left join.
I am attempting to return a whole lot of results (and sometimes whole tables) this is why includes does not suit my needs.
Active Record provides a #column_names method that returns an array of column names.
Usage example: User.column_names
two options
Model.column_names
or
Model.columns.map(&:name)
Example
Model named Rabbit with columns name, age, on_facebook
Rabbit.column_names
Rabbit.columns.map(&:name)
returns
["id", "name", "age", "on_facebook", "created_at", "updated_at"]
This is just way active record's inspect method works: it only lists the column's from the model's table. The attributes are still there though
record.blah
will return the blah attribute, even if it is from another table. You can also use
record.attributes
to get a hash with all the attributes.
However, if you have multiple columns with the same name (e.g. both tables have an id column) then active record just mashes things together, ignoring the table name.You'll have to alias the column names to make them unique.
Okay I have been wanting to do something that's more efficient for a while.
Please note that for very few results, include works just fine. The code below works better when you have a lot of columns you'd like to join.
In order to make it easier to understand the code, I worked out an easy version first and expanded on it.
First method:
# takes a main array of ActiveRecord::Base objects
# converts it into a hash with the key being that object's id method call
# loop through the second array (arr)
# and call lamb (a lambda { |hash, itm| ) for each item in it. Gets called on the main
# hash and each itm in the second array
# i.e: You have Users who have multiple Pets
# You can call merge(User.all, Pet.all, lambda { |hash, pet| hash[pet.owner_id].pets << pet }
def merge(mainarray, arr, lamb)
hash = {}
mainarray.each do |i|
hash[i.id] = i.dup
end
arr.each do |i|
lamb.call(i, hash)
end
return hash.values
end
I then noticed that we can have "through" tables (nxm relationships)
merge_through! addresses this issue:
# this works for tables that have the equivalent of
# :through =>
# an example would be a location with keywords
# through locations_keywords
#
# the middletable should should return as id an array of the left and right ids
# the left table is the main table
# the lambda fn should store in the lefthash the value from the righthash
#
# if an array is passed instead of a lefthash or a righthash, they'll be conveniently converted
def merge_through!(lefthash, righthash, middletable, lamb)
if (lefthash.class == Array)
lhash = {}
lefthash.each do |i|
lhash[i.id] = i.dup
end
lefthash = lhash
end
if (righthash.class == Array)
rhash = {}
righthash.each do |i|
rhash[i.id] = i.dup
end
righthash = rhash
end
middletable.each do |i|
lamb.call(lefthash, righthash, i.id[0], i.id[1])
end
return lefthash
end
This is how I call it:
lambmerge = lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
Location.merge_through!(Location.all, Keyword.all, LocationsKeyword.all, lambmerge)
Now for the complete method (which makes use of merge_through)
# merges multiple arrays (or hashes) with the main array (or hash)
# each arr in the arrs is a hash, each must have
# a :value and a :proc
# the procs will be called on values and main hash
#
# :middletable will merge through the middle table if provided
# :value will contain the right table when :middletable is provided
#
def merge_multi!(mainarray, arrs)
hash = {}
if (mainarray.class == Hash)
hash = mainarray
elsif (mainarray.class == Array)
mainarray.each do |i|
hash[i.id] = i.dup
end
end
arrs.each do |h|
arr = h[:value]
proc = h[:proc]
if (h[:middletable])
middletable = h[:middletable]
merge_through!(hash, arr, middletable, proc)
else
arr.each do |i|
proc.call(i, hash)
end
end
end
return hash.values
end
Here's how I use my code:
def merge_multi_test()
merge_multi!(Location.all,
[
# each one location has many s3_images (one to many)
{ :value => S3Image.all,
:proc => lambda do |img, hash|
if (img.imageable_type == 'Location')
hash[img.imageable_id].s3_images << img
end
end
},
# each location has many LocationsKeywords. Keywords is the right table and LocationsKeyword is the middletable.
# (many to many)
{ :value => Keyword.all,
:middletable => LocationsKeyword.all,
:proc => lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
}
])
end
You can modify the code if you wish to lazy load attributes that are one to many (such as a City is to a Location) Basically, the code above won't work because you'll have to loop through the main hash and set the city from the second hash (There is no "city_id, location_id" table). You could reverse the City and Location to get all the locations in the city hash then extract back. I don't need that code yet so I skipped it =)