Understanding the "where" and how to use it in Ruby on Rails - ruby-on-rails

I have a table named product_variations, and this table has_many products.
When I go to Rails console, I can do this:
2.6.3 :036 > var = ProductVariation.first
ProductVariation Load (0.2ms) SELECT `product_variations`.* FROM `product_variations` ORDER BY `product_variations`.`id` ASC LIMIT 1
=> #<ProductVariation id: 16, product_id: 1024, color_id: 19, quantity: 1, size: "GG", sub_sku: 10, created_at: "2020-02-18 14:41:46", updated_at: "2020-02-18 14:41:46">
2.6.3 :037 > var.size
=> "GG"
So, that's working. But when I try to find something there using where, I got this intead:
2.6.3 :038 > var = ProductVariation.where(product_id: 1024, sub_sku: 10)
ProductVariation Load (0.4ms) SELECT `product_variations`.* FROM `product_variations` WHERE `product_variations`.`product_id` = 1024 AND `product_variations`.`sub_sku` = 10 LIMIT 11
=> #<ActiveRecord::Relation [#<ProductVariation id: 16, product_id: 1024, color_id: 19, quantity: 1, size: "GG", sub_sku: 10, created_at: "2020-02-18 14:41:46", updated_at: "2020-02-18 14:41:46">]>
2.6.3 :039 > var.size
(0.2ms) SELECT COUNT(*) FROM `product_variations` WHERE `product_variations`.`product_id` = 1024 AND `product_variations`.`sub_sku` = 10
=> 1
Note that when I try to use var.size, the console do another search at the database, but now using SELECT COUNT(*) instead of just SELECT, and the output is 1 (while should be GG, why?). For what I understood until now, where can return many results. Thats why it uses COUNT? But how can I use the result? In this app I'm working at, on the product_variations table I'll have only one single match with product_id and sub_sku so I want it to return this line, but can't figure out how to use where to do this.

you can use .first after the where
var = ProductVariation.where(product_id: 1024, sub_sku: 10).first
or use the find_by method
var = ProductVariation.find_by(product_id: 1024, sub_sku: 10)
which will do the same thing

Related

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

Group by part of attribute in hash

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

Count the records where a field doesn't equal value X

I'm trying to perform a count on results where a field is not equal to a specific value, but it always fails to returns results.
For instance, assuming Company and Products have a one to many relationship, I might get the following array back from ActiveRecord by querying for Company.find(63).products (which would be the SQL equivalent of SELECT "products".* FROM "products" WHERE "products"."company_id" = 63;):
<Product id: 1, company_id: 63, foo_id: 1>,
<Product id: 2, company_id: 63, foo_id: 3>,
<Product id: 3, company_id: 63, foo_id: nil>,
<Product id: 4, company_id: 63, foo_id: nil>
However, if I try to extend the above query to count everything but the first record with the following:
Company.find(63).products.where("foo_id != ?", 1).count
Which in SQL is:
SELECT COUNT(*) FROM "products" WHERE "products"."company_id" = 63 AND (foo_id != 1)
I always seem to get 1 back as a result when I expect to see 3. Why is this happening and how can I get it to count correctly?
If it is like you say you should get 1 instead of 0. What you are looking for is DISTINCT FROM as = and != on null types both return unknown as the result.
SELECT COUNT(*) FROM
products
WHERE products.company_id = 63 AND (foo_id IS DISTINCT FROM 1)
http://sqlfiddle.com/#!1/8b7a0/3
And as for further information. DISTINCT FROM is PostgreSQL exclusive so the standard version of doing this would be:
SELECT COUNT(*) FROM
products
WHERE products.company_id = 63 AND (foo_id<>1 OR foo_id IS NULL)
have you tried using <> instaed of !=
<>
Company.find(63).products.where("foo_id != ?", 1).count
should be
Company.find(63).products.where("foo_id <> ?", 1).count
but you should take it one step further:
class Product
def self.not_foo(foo_id)
where("foo_id <> ?", foo_id)
end
end
now
Company.find(63).products.not_foo(1).count

Resources