Group by part of attribute in hash - ruby-on-rails

I have a model called coverage that looks like this
1.9.3p429 :005 > Coverage.new
=> #<Coverage id: nil, postcode: nil, name: nil, created_at: nil, updated_at: nil>
Here is an example record:
1.9.3p429 :006 > Coverage.find(10)
Coverage Load (7.3ms) SELECT "coverages".* FROM "coverages" WHERE "coverages"."id" = $1 LIMIT 1 [["id", 10]]
=> #<Coverage id: 10, postcode: "N10", name: "N10 - Muswell Hill", created_at: "2013-05-22 14:42:37", updated_at: "2013-05-22 14:42:37">
I've got over 300 postcodes and I want to group them by some values I have in this array
group = ['N','E','EC','LS','TS']
So I would like to do
#postcodes = Coverage.all
run it through something with the above array to get the following hash
#postcode_hash = { 'N' => [ '#Active Record for N1', '#Active Record for N2' ], 'E' => [ '#Active Record for E1', '#Active Record for E2' ] }
# note: not complete should contain all index from the above array

You can use the .group_by{} method:
#postcodes = Coverage.all
#postcodes_hash = #postcodes.group_by{ |c| c.postcode.gsub(/[0-9]/, '') }
Take a look at the group_by documentation:
http://apidock.com/rails/Enumerable/group_by
There is the explicit version of above:
#postcode_hash = {}
group = ['N','E','EC','LS','TS']
group.each{ |code| #postcode_hash[code] = [] }
#postcodes = Coverage.scoped # similar to .all but better
#postcodes.map do |coverage|
code = coverage.postcode.gsub(/[0-9]/, '') # takes off all the numbers of the postcode
#postcode_hash[code] << coverage if #postcode_hash[code]
end

Related

ActiveRecord query returns different results for two in-memory instances of the same database object

In a console session, repeatably, If I access a certain model instance by pulling out of an array, I get incorrect results when calling a method on that object. However, if I load the same object directly from the database with find(id), the same method returns the correct results.
How can this happen? The really crazy part is that for the first two elements of the array, everything is normal. As you can see at the end of the console output, results for the first two elements in the array (they are all the same type and all have two Link children) make sense. The latter five should not be empty.
# Instance from an array
irb(main):417:0> the_array = User.find(89).utterances.select{|u| u.links.count > 1}
=> [#<Utterance id: ...>, #<Utterance id: 6931, ...>, #<Utterance id: 6935, ...>, #<Utterance id: 6944, ...>, #<Utterance id: 6955, ...>, #<Utterance id: 6989, ...>, #<Utterance id: 7014, ...>]
irb(main):418:0> from_array = the_array.last
=> #<Utterance id: 7014, ...>
irb(main):419:0> from_array.class
=> Utterance(id: integer, index: integer, begins_at: float, text: text, created_at: datetime, updated_at: datetime, ends_at: float, recording_id: integer)
irb(main):420:0> from_array.links.class
=> Link::ActiveRecord_Associations_CollectionProxy
irb(main):421:0> from_array.links
=> #<ActiveRecord::Associations::CollectionProxy []>
# Instance from find
irb(main):422:0> from_find = Utterance.find(7014)
Utterance Load (1.8ms) SELECT "utterances".* FROM "utterances" WHERE "utterances"."id" = $1 LIMIT $2 [["id", 7014], ["LIMIT", 1]]
=> #<Utterance id: 7014, ...>
irb(main):423:0> from_find.class
=> Utterance(id: integer, index: integer, begins_at: float, text: text, created_at: datetime, updated_at: datetime, ends_at: float, recording_id: integer)
irb(main):424:0> from_find.links.class
=> Link::ActiveRecord_Associations_CollectionProxy
irb(main):424:0> ap from_find.links
[
[0] #<Link:0x000055fc7f154150> {
:id => 212,
#...
},
[1] #<Link:0x000055fc7f15feb0> {
:id => 213,
#...
}
]
irb(main):425:0>
# But for the first two elements of the array, no problem
irb(main):486:0> the_array.each{|u| puts ap u.links}
[
[0] #<Link:0x000055fc80cc34e0> {
:id => 208,
...
},
[1] #<Link:0x000055fc80cc3328> {
:id => 209,
...
}
]
[
[0] #<Link:0x000055fc80cc7180> {
:id => 211,
...
},
[1] #<Link:0x000055fc80cc6fc8> {
:id => 210,
...
}
]
[]
[]
[]
[]
[]
Associations are cached. from_array.links is getting the cached set from when it was first called, from_find.links is getting a fresh copy from the database. Presumably some links were added between the first call to from_array.links and from_find.links.
If you run from_array.links.reload it will refresh the cached association and you should get the same result as from_find.links.
You can avoid stale association caches by altering the association using methods on the collection. For example, if you want to create a new link...
# This will update the utterance.links cache
utterance.links.create!( some: "attribute" )
# This will not.
link = Links.create!(
some: "attribute",
utterance: utterance
)
# This will.
utterance.links << link
# This will not.
link.destroy
# This will.
utterance.links.destroy(link)
Note that: User.find(89).utterances.select{|u| u.links.count > 1} is inefficient. It must load each Utterance and count its links individually, known as an N+1 query (or 1+N).
It's more efficient to do this with a join and a group by.
User.find(89).utterances
.joins(:links)
.group('utterances.id')
.having('count(utterances.id) > 1')
The answer turns out to be that my console session was somehow corrupt. I took the following steps and then all of the calls to the links mtehod on the Utterance model, from the array, worked as expected.
Quit the console
rails c
Paste this code:
the_array = Recording.find(89).utterances.select{|u| u.links.count > 1}
from_array = the_array.last
from_array.class
from_array.links.class
from_array.links
from_find = Utterance.find(7014)
ap from_find.id
ap from_find.class
from_find.links.class
ap from_db.links
the_array.each{|u| puts ap u.links}
Step 3 is the same thing I was doing in the previous console session. Apparently I had somehow borked the previous session's stack.

How do I write an array of attributes to a column after having first cycled through a collection?

So basically what I want to happen is, on my n.parents attribute I would like to set a value like [val1, val2, val3, val4].
My setter method looks like this:
def parents=(*parents)
write_attribute(self.base_class.ancestry_column,
if parents.nil?
nil
else
parents.map(&:child_ancestry)
end
)
end
But when I run this, I get this error:
> n.parents= a,b
NoMethodError: undefined method `child_ancestry' for #<Array:0x007f9fb0072fb8>
In this case, val1 = a.child_ancestry, val2 = b.child_ancestry...but in theory, I should be able to do n.parents= a,b,c,d,e,f and it should work just as well.
P.S. I am trying to write these to the ancestry_column of the base_class of the object I am updating.
Edit 1
After trying both answers below from #zealoushacker and #nathanvda, I keep getting the same undefined method 'child_ancestry' for #<Array....> error.
However, if in my console I just do any of those map operations, it seems to return fine...so I am even more confused.
Example:
[8] pry(main)> n
=> #<Node id: 36, family_tree_id: 2, created_at: "2015-01-28 23:19:28", updated_at: "2015-01-28 23:19:28", name: "Mesty", ancestry: "13/35", ancestry_depth: 0, max_tree_depth: 0>
[9] pry(main)> n.parents
Node Load (0.4ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> [#<Node id: 35, family_tree_id: 2, created_at: "2015-01-28 23:17:36", updated_at: "2015-01-28 23:17:36", name: "Testy", ancestry: "13", ancestry_depth: 0, max_tree_depth: 0>]
[10] pry(main)> n.parents.map(&:child_ancestry)
Node Load (0.4ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> ["13/35"]
[11] pry(main)> n.parents.flatten.map(&:child_ancestry)
Node Load (0.3ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> ["13/35"]
You need to write:
parents.flatten.map(&:child_ancestry)
because the *parents splat argument contains an array of arrays, which looks something like this if you inspect it:
[[#<ChildAncestry:0x000001018d1010>, #<ChildAncestry:0x000001018d0fe8>]]
Take a look at Array#flatten.
It takes the above and converts it to something like:
[#<ChildAncestry:0x000001018d1010>, #<ChildAncestry:0x000001018d0fe8>]
on which you may then use map as you had.
Weird to use the splat operator in an assignment, why not do something like
def parents=(new_parents)
ancestry = if new_parents.nil?
nil
else
new_parents = [new_parents] unless new_parents.is_a?(Array)
new_parents.map(&:child_ancestry).join('/')
end
write_attribute(self.base_class.ancestry_column, ancestry)
end
and then you can still write
n.parents = a,b
(which is converted to an array automatically in the assignment).
The splat operator will wrap the given parameter in an array again. So just drop the splat operator. It is used in function calls, on assignments it does not make any sense imho.
trying it manually:
The thing you need to try in the console to see if it works:
> my_parents = a,b
> ancestry = my_parents.map(&:child_ancestry)
> n.ancestry_column = ancestry

select all the values of the records belonging to a specific field of the database and save them in a array Active Record

I have the following set of records . I want to save in a array called #texts only
the values of the field text doing an each o a for
1.9.3-p547 :074 > Tweet.all
Tweet Load (0.3ms) SELECT "tweets".* FROM "tweets"
=> [#<Tweet id: 1, text: "hola a todos", zombie_id: 5, created_at: "2014-12-29 23:52:40", updated_at: "2014-12-29 23:52:40">, #<Tweet id: 2, text: "hola como estas", zombie_id: 5, created_at: "2014-12-30 00:09:40", updated_at: "2014-12-30 00:09:40">, #<Tweet id: 3, text: "hello", zombie_id: 5, created_at: "2014-12-30 12:44:41", updated_at: "2014-12-30 12:44:41">]
1.9.3-p547 :075 >
Example
#texts=["hola a todos","hola cmo estas", "hello"];
I would do this:
#texts = Tweet.all.map(&:text)
Or:
#texts = Tweet.pluck(:text)
You should use pluck, as for the documentation:
Use pluck as a shortcut to select one or more attributes without
loading a bunch of records just to grab the attributes you want.
This way you are only select the desired columns and not the "full" record.

Nested group query in Rails

I have a list of Answers:
Answer.last
Answer Load (0.8ms) SELECT "answers".* FROM "answers" ORDER BY "answers"."id" DESC LIMIT 1
=> #<Answer id: 235, question_id: 15, choice_id: 23, user_id: 3, created_at: "2013-08-28 23:51:24", updated_at: "2013-08-28 23:51:24", club_id: 11>
As you can see, each Answer belongs to a question, choice, user and club. I'm trying to create a nested group query to produce a hash of:
{ club_id { choice_id => answers_count, choice_id => answers_count}, etc, etc }
What I have so far:
Answer.where(:question_id => 14).group(:club_id, :choice_id).count
Which produces:
{[4, 21]=>7, [11, 21]=>2, [4, 22]=>4, [11, 22]=>7}
This is:
{ [club_id, choice_id] => answers_count, etc, etc }
Is there anyway to do what I require in one query or will I have to merge/re-jig this hash? If so, how can I do this?
As #tadman says, the result you're seeing is all the query can get you. If you want a custom format, you'll need to run it through an algorithm. Something like this should work for you:
result = Answer.where(:question_id => 14).group(:club_id, :choice_id).count
better_result = result.each_with_object({}) do |((club_id, choice_id), answers_count), m|
m[club_id] ||= {}
m[club_id][choice_id] = answers_count
end

Rails model logic giving wrong result

When i do some logic on a colum value after copied to some variable, my actual colum value on the object is getting changed, my model methods are,
def copy_configuration_values
element_positions_dup = element_positions.dup
cert_element.read_attribute(:field_names)["configuration_values"].each { |k, v|
element_positions_dup["configuration_values"][k] = v if configuration_value_present?(k)
}
element_positions_dup
end
def configuration_value_present?(configuration)
element_positions["configuration_values"] && element_positions["configuration_values"][configuration]
end
And when i call this method from console like below,
1.9.3p194 :001 > t = CertTemplate.find 30
CertTemplate Load (0.3ms) SELECT `cert_templates`.* FROM `cert_templates` WHERE `cert_templates`.`id` = 30 LIMIT 1
=> #<CertTemplate id: 30, name: "aaaaaaaaaaaq", cert_element_id: 22, element_positions: {"configuration_values"=>{"Program"=>"9.523810492621529,24.627720154437824"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}, created_at: "2012-08-08 07:18:33", updated_at: "2012-08-16 08:03:52", image_file_name: "Marksheet_Updated.jpg", image_content_type: "image/jpeg", image_file_size: 2236497, image_updated_at: "2012-08-08 07:18:33">
1.9.3p194 :002 >
1.9.3p194 :003 > t.copy_configuration_values
CertElement Load (0.2ms) SELECT `cert_elements`.* FROM `cert_elements` WHERE `cert_elements`.`id` = 22 LIMIT 1
=> {"configuration_values"=>{"Program"=>"2"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}
1.9.3p194 :004 >
1.9.3p194 :005 > t
=> #<CertTemplate id: 30, name: "aaaaaaaaaaaq", cert_element_id: 22, element_positions: {"configuration_values"=>{"Program"=>"2"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}, created_at: "2012-08-08 07:18:33", updated_at: "2012-08-16 08:03:52", image_file_name: "Marksheet_Updated.jpg", image_content_type: "image/jpeg", image_file_size: 2236497, image_updated_at: "2012-08-08 07:18:33">
1.9.3p194 :006 >
My actual column value is getting changed, what i am doing wrong. Advance thanks.
It looks like the problem is you are assigning references to nested hashes when you iterate through the key values, instead of copies.
Like specifically, "custom_fields" key in both element_positions and element_positions_dup will point to the same Hash object as the value because you assign it without duplicating it. To fix it try
...
element_positions_dup["configuration_values"][k] = v.dup if configuration_value_present?(k)
...
Edit: yeah you need deep copies
Use Marshal serialization

Resources