Ruby's nil? function not working - ruby-on-rails

I am using Ruby 1.9.3.
This is what is happening in the console:
1.9.3-p392 :028 > p = Product.find(1)
Product Load (0.4ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1
=> #<Product id: 1, name: "Product 4", image: nil, available: true>
1.9.3-p392 :029 > p.image
=>
1.9.3-p392 :030 > p.image.nil?
=> false
When the product is returned, the image is obviously nil, but when I try and get the value (p.image) it does not show anything.
Why is the p.image.nil? command not returning true?

Use .blank? it'll return true if the attribute is nil or empty

Related

Understanding the "where" and how to use it in 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

Rails scope doesn't return correct data

When I use scope which I prepared in model, rails returns incorrect data.
My Model:
class CurrencyRate < ActiveRecord::Base
scope :eur_today, -> {where(currency: "eur").where(date: Time.now.in_time_zone.to_date).first}
end
Inforamation from rails console:
2.3.0 :011 > CurrencyRate.eur_today
CurrencyRate Load (0.2ms) SELECT "currency_rates".* FROM
"currency_rates" WHERE "currency_rates"."currency" = ? AND
"currency_rates"."date" = ? ORDER BY "currency_rates"."id" ASC LIMIT 1
[["currency", "eur"], ["date", "2017-08-09"]]
CurrencyRate Load (0.2ms) SELECT "currency_rates".* FROM
"currency_rates"
=> #<ActiveRecord::Relation [#<CurrencyRate id: 2, currency: "eur",
sale: 4.248546249999998, purchase: 4.265333125, date: "2017-08-08",
created_at: "2017-08-08 20:52:08", updated_at: "2017-08-08 20:54:10",
sale_percentage_diff: nil, purchase_percentage_diff: nil>]>
When I use the same query like in scope, returned data is correct:
2.3.0 :012 > CurrencyRate.where(currency: "eur").where(date: Time.now.in_time_zone.to_date).first
CurrencyRate Load (0.2ms) SELECT "currency_rates".* FROM
"currency_rates" WHERE "currency_rates"."currency" = ? AND
"currency_rates"."date" = ? ORDER BY "currency_rates"."id" ASC LIMIT 1
[["currency", "eur"], ["date", "2017-08-09"]]
=> nil
Why scope doesn't work correct?
Because scopes must return an ActiveRecord::Relation. Get rid of the first call in your scope and use that outside the scope. Why? Because scopes have to be chainable.

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

How to check for database changes of in-memory records?

I want to check if an ActiveRecord instance was changed database-wise. Something like:
p1 = Product.first
p1.name #=> "some name"
p2 = Product.first
p2.name = "other name"
p2.save #=> true
p1.database_changed? #=> true
I'm currently comparing the record's attributes to the persisted attributes:
class Product < ActiveRecord::Base
def database_changed?
Product.find(id).attributes != attributes
end
end
This seems to work, but I'm wondering if there is a built-in way to find database changes?
After Зелёный's comment I've reviewed ActiveModel::Dirty and realized that it almost does what I want. There's already an in-memory state (the record's attributes) and a database state (handled by ActiveModel::Dirty). I just need a method to update the database state, leaving the in-memory state unchanged:
def refresh
#changed_attributes = {}
fresh_object = self.class.unscoped { self.class.find(id) }
fresh_object.attributes.each do |attr, orig_value|
#changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, #attributes[attr])
end
self
end
#changed_attributes is ActiveModel::Dirty's hash to store changed values. We obviously have to reset it.
fresh_object is the same record, freshly fetched from the database (this line comes from reload, thanks emaillenin).
Within the loop, each (fresh) attribute is compared to the corresponding (in-memory) attribute. If they differ, it is added to the #changed_attributes hash. This line comes from ActiveRecord's dup method. (it's actually from a private method called by dup, and _field_changed is private, too. It might be better to use ActiveRecord's public API, but I was lazy)
Finally, refresh returns self for convenience, just like reload does.
Here's an example usage:
p1 = Product.first
p1.name #=> "some name"
p1.changed? #=> false
p2 = Product.first
p2.name = "other name"
p2.save #=> true
p1.refresh
p1.name #=> "some name"
p1.changed? #=> true
p1.name_changed? #=> true
p1.name_was #=> "other name"
p1.name = "other name"
p1.name_changed? #=> false
p1.changed? #=> true
p1.changes #=> {"updated_at"=> [Tue, 29 Jul 2014 21:58:57 CEST +02:00, Tue, 29 Jul 2014 15:49:54 CEST +02:00]}
def database_changed?
self.class.where(self.class.arel_table[:updated_at].gt(updated_at)).exists? self
end
The Rails way to to do this is to use reload method on the ActiveRecord object.
def database_changed?
attributes != reload.attributes
end
Terminal 1
2.1.2 :001 > c = Car.find(1)
Car Load (0.4ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.database_changed?
Car Load (0.1ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> false
2.1.2 :003 > c.database_changed?
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> false
2.1.2 :004 > c.database_changed?
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> true
Terminal 2
2.1.2 :001 > c = Car.find(1)
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.model = 'A5'
=> "A5"
2.1.2 :003 > c.save!
(0.2ms) begin transaction
SQL (0.3ms) UPDATE "cars" SET "model" = ?, "updated_at" = ? WHERE "cars"."id" = 1 [["model", "A5"], ["updated_at", "2014-07-29 11:15:32.845875"]]
(1.2ms) commit transaction
=> true
2.1.2 :004 >

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