I have a Postgres database. One of the columns in one of my tables consists of arrays. It's called aliases. I'm trying to merge two instances but when I try to merge the arrays they are not saving.
irb(main):001:0> original
=> #<Thing id: 1, name: "Foo", aliases: ["Foo"]>
irb(main):002:0> duplicate
=> #<Thing id: 2, name: "Bar", aliases: ["Bar"]>
irb(main):003:0> original.aliases | duplicate.aliases
=> ["Foo", "Bar"]
irb(main):004:0> original.save!
(0.3ms) BEGIN
Thing Exists (0.8ms) SELECT 1 AS one FROM "thing" WHERE "things"."name" = $1 AND ("thing"."id" != $2) LIMIT $3 [["name", "Foo"], ["id", 1], ["LIMIT", 1]]
(0.3ms) COMMIT
Thing Store (6.1ms) {"id":1}
=> true
But then when I check original the aliases have not merged.
irb(main):005:0> original
=> #<Thing id: 1, name: "Foo", aliases: ["Foo"]>
I tried several different ways to insert duplicate.aliases into original.aliases but nothing seems to be saving. Any ideas why? Am I missing something?
Edit:
I'm on Rails 5.1.4 and Ruby 2.5.0p0
Are you on Rails 3? I've noticed that Rails3 activerecord array handling does not like inline updates. You need to use a temporary variable, augment that variable and then assign it back.
aliases = original.aliases # make a temp variable
aliases = aliases | duplicate.aliases # modify temp variable
original.aliases = aliases # assign back
original.save! # now you can save
I encountered this myself when I was trying to push onto an array:
original.aliases << "something" # aliases would never get changed
Related
I notices in the console an unusual user of to_a method in ActiveRecord_Relation. I'll reproduce it below:
Selecting from a table to the variable a:
a = Crypto::ExchangeQuotation.all
[DEBUG] Crypto::ExchangeQuotation Load (121.1ms) SELECT "crypto_exchange_quotations".* FROM "crypto_exchange_quotations" LIMIT $1 [["LIMIT", 11]]
Calling a:
2.4.1 :003 > a
[DEBUG] Crypto::ExchangeQuotation Load (0.8ms) SELECT "crypto_exchange_quotations".* FROM "crypto_exchange_quotations" LIMIT $1 [["LIMIT", 11]]
a to array:
2.4.1 :004 > a.to_a
[DEBUG] Crypto::ExchangeQuotation Load (0.9ms) SELECT "crypto_exchange_quotations".* FROM "crypto_exchange_quotations"
Calling a again:
2.4.1 :005 > a
=> #<ActiveRecord::Relation [#<Crypto::ExchangeQuotation id: 1, exchange_code: "ARN", base_currency_code: "BRL", currency_code: "BTC", origin: "bitvalor", created_at: "2018-03-15 23:24:06", updated_at: "2018-03-15 23:24:06", buy: 17810.0, sell: 17810.0>,
Calling to_a in the variable, calling it again after is really avoiding rerun the query again? If yes, why? Because what is returned in a still is an ActiveRecord_Relation.
Calling to_a is not mutating base object, but it is duplicating this and returning it as an array. Here look ad code of to_a:
def to_a
records.dup
end
To really change it to array you have to override variable a
a = a.to_a
How do you restrict strings allowed in a string field to specific words?
Example: I have a model attribute animal:string that I would like to exclusively accept ["dog", "cat", "bird", "fish"]. Every thing else would make the object invalid.
Add an inclusion validation to your model:
validates :animal, inclusion: { in: %w(dog cat bird fish) }
As I said, I'd go with Rails Enum feature.
class ModelName < ActiveRecord::Base
enum animals: %w(dog cat)
# ...
end
There is one gotcha which you might notice since this is called enum: you will need to update your database column to be an integer value. Rails will do implicit mapping between the integer value and the index of the value in the array automatically
If you go with Enum feature, having the single line of code, Rails will generate several helper methods for you:
# Query method
model_name.dog?
model_name.cat?
#..
# Action method
model_name.cat!
model_name.dog!
#..
# List of statuses and their corresponding values in the database.
model_name.animals
Tried and Tested :
[arup#app]$ rails c
Loading development environment (Rails 4.1.1)
[1] pry(main)> Pet.create!(animals: 'cat')
(0.3ms) BEGIN
SQL (0.9ms) INSERT INTO "pets" ("animals", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["animals", 1], ["created_at", "2015-02-19 18:27:28.074640"], ["updated_at", "2015-02-19 18:27:28.074640"]]
(42.2ms) COMMIT
=> #<Pet id: 5, animals: 1, created_at: "2015-02-19 18:27:28", updated_at: "2015-02-19 18:27:28">
[2] pry(main)> Pet.create!(animals: 'cow')
ArgumentError: 'cow' is not a valid animals
from /home/arup/.rvm/gems/ruby-2.1.2#app/gems/activerecord-4.1.1/lib/active_record/enum.rb:103:in 'block (3 levels) in enum'
[3] pry(main)> Pet.animals
=> {"dog"=>0, "cat"=>1}
[5] pry(main)> Pet.first.dog?
Pet Load (0.8ms) SELECT "pets".* FROM "pets" ORDER BY "pets"."id" ASC LIMIT 1
=> false
[6] pry(main)> Pet.first.cat?
Pet Load (0.7ms) SELECT "pets".* FROM "pets" ORDER BY "pets"."id" ASC LIMIT 1
=> true
[7] pry(main)> Pet.first.cow?
Pet Load (0.7ms) SELECT "pets".* FROM "pets" ORDER BY "pets"."id" ASC LIMIT 1
NoMethodError: undefined method 'cow?' for #<Pet:0xbf8455c>
from /home/arup/.rvm/gems/ruby-2.1.2#app/gems/activemodel-4.1.1/lib/active_model/attribute_methods.rb:435:in `method_missing'
[8] pry(main)>
Pet.create!(animals: 'cow') throws error, which confirmed that, Pet model wouldn't accept anything other than the enum values.
you can use select field in your form, and write this in your model:
module Animal
dog = 1
cat = 2
bird = 3
fish = 4
end
and in tour form:
<%= f.select :animal, { "dog" => 1, "cat" => 2, "bird" => 3, "fish" => 4} %>
I'm trying to understand the following behaviour:
irb(main):016:0* pg = ProductGroup.find(1)
irb(main):017:0> pg.good_type_ids
=> [1]
irb(main):018:0> pg.update({"good_type_ids"=>["", "1"]})
irb(main):019:0> pg.changed?
=> false
irb(main):020:0> pg.good_type_ids
=> ["1"]
The code above mimics a form update. The good_type_ids are selected ids's of a multi select. Although the value of the array changes from [1] to ["1"], the changed? method returns false. The update method seems also be smart enough to strip the empty string.
irb(main):021:0> pg = ProductGroup.find(1)
irb(main):022:0> pg.good_type_ids
=> [1]
irb(main):023:0> pg.attributes = {"good_type_ids"=>["", "1"]}
=> {"good_type_ids"=>["", "1"]}
irb(main):024:0> pg.changed?
=> true
irb(main):025:0> pg.good_type_ids
=> ["", "1"]
In this second example I'm trying to apply the changed params from the form to the object. I don't want to save it to the dbs!
Somehow the attributes method behaves different than the update method and good_type_ids will be ["","1"] after the method was invoked.
It seems that the type of the column in the dbs is taken into account by the "update" and "attributes" method, only for arrays (postgress) this doesn't seem to work.
irb(main):030:0* pg.attributes = {"recourse_days"=>5}
=> {"recourse_days"=>5}
irb(main):031:0> pg.changed?
=> true
irb(main):039:0* pg.recourse_days
=> 5
recourse_days are defined as integer, during post the controller receives a string and converts it correctly to an integer.
Schema.rb: t.integer "good_type_ids", default: [], array: true
As temporary work arround I have put this in the update method of the controller
params["good_type_ids"].reject!(&:blank?)
params["good_type_ids"].map!(&:to_i)
UPD
Strings conversion issues and nil elements saving to pgsql seems to be fixed in rails 4.2.0, i've heard whole type casting system is pretty much revised there:
> doc.test_digit = ["1"] #=> ["1"]
> doc.test_digit #=> [1]
> doc.test_string #=> ["2"]
> doc.test_string = [2] #=> [2]
> doc.test_string #=> ["2"]
> doc.attributes = {'test_digit'=>["", "2"]}
=> {"test_digit"=>["", "2"]}
> doc.test_digit #=> [nil, 2]
> doc.save
(0.9ms) BEGIN
SQL (1.8ms) UPDATE "documents" SET "test_digit" = $1, "updated_at" = $2 WHERE "documents"."id" = $3 [["test_digit", "{NULL,2}"], ["updated_at", "2015-02-26 00:32:25.326735"], ["id", 1]]
(3.7ms) COMMIT
=> true
> doc.reload
Document Load (1.7ms) SELECT "documents".* FROM "documents" WHERE "documents"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Document id: 1, name: "test1", test_digit: [nil, 2], test_string: ["2"], created_at: "2015-02-25 23:51:32", updated_at: "2015-02-26 00:32:25">
> doc.test_digit #=> [nil, 2]
I used following migrations:
t.integer :test_digit, array: true, default: []
t.string :test_string, array: true, default: []
Update method updates the db, thus your model attributes are no longer dirty.
Good guide on this
The update method seems also be smart enough to strip the empty string.
This is not normal behavior, according to the source of pgsql adapter/array_parser, empty array elements should be saved and loaded as any other values. (this functionality was added on Aug 20, 2012 to the adapter just in case you are using an older version)
[1] pry(main)> User.first
User Load (0.4ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 3, email: "chasity#kiehn.net", encrypted_password: "$2a$10$lqsgKvQuz9iSIu/..FMRJu76H9KNhfVz5x9DmxphC0TK...", reset_password_token: ... >
[2] pry(main)> User.find(1)
User Load (12.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
=> #<User id: 1, email: "admin#example.com", encrypted_password: "$2a$10$pGDcv0/EgiDk4KcZN9pli.evx5Ni1qOoujQD15HgWH8Y...", reset_password_token: ... >
[3] pry(main)> Product.first
Product Load (0.6ms) SELECT "products".* FROM "products" LIMIT 1
=> #<Product id: 1, name: "Ruby on Rails Jr. Spaghetti", created_at: "2012-01-25 10:13:26", updated_at: "2012-01-25 10:13:26", properties: ...>
This is happening in the console and during runtime and only for the User model. What could cause such behaviour?
You haven't specified an order - the database is free to order the results in any way it wants. It could be primary key order, it could be how things are laid out on disc but without an order clause there are no guarantees.
If you want the first record in a specific order, then make sure you ask for it
User.first is the same as User.all.first, there is no specified order, so the DB returns the list of element in any order. If you want to get the User with smallest id, use User.order("id").first.
If your request returns several elements and you want to pick any of them, you may want to use first without specified order.
I'm testing with Rspec a model named Solutions which has many Likes. Solution stores how many Likes it have (counter_cache). It has a "likes_count" attribute (and respective db field).
When I create a Like record associated to a Solution, I expect that the solution attribute "likes_count" should be updated from nil to 1. When I do that in console, it works.
But when I run the spec, doing the SAME THING I do in console, it update TWICE the "likes_count" field, setting it to 2.
Take a look (in console) WORKING:
irb(main):001:0> solution = Factory(:solution)
irb(main):004:0> solution.likes_count
=> nil
irb(main):006:0> like = Factory(:like, :likeable => solution)
=> #<Like id: 1, user_id: 2, likeable_id: 1, likeable_type: "Solution",
created_at: "2011-11-23 19:31:23", updated_at: "2011-11-23 19:31:23">
irb(main):007:0> solution.reload.likes_count
=> 1
Take a look at the spec result NOT WORKING:
1) Solution counter cache should be increased when a like is created
Failure/Error: subject.reload.likes_count.should be 1
expected #<Fixnum:3> => 1
got #<Fixnum:5> => 2
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
'actual.should == expected' if you don't care about
object identity in this example.
# ./spec/models/solution_spec.rb:45:in `block (3 levels) in <top (required)>'
Here is the spec:
describe "counter cache" do
let(:solution) { Factory(:solution) }
it "should be increased when a like is created" do
Factory(:like, :likeable => solution)
solution.reload.likes_count.should be 1
end
end
I took a look at test.log and I realized that the db query that updates the counter cache column was called two times in the test.
SQL (0.5ms) INSERT INTO "likes" ("created_at", "likeable_id", "likeable_type", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?) [["created_at", Wed, 23 Nov 2011 19:38:31 UTC +00:00], ["likeable_id", 121], ["likeable_type", "Solution"], ["updated_at", Wed, 23 Nov 2011 19:38:31 UTC +00:00], ["user_id", 204]]
SQL (0.3ms) UPDATE "solutions" SET "likes_count" = COALESCE("likes_count", 0) + 1 WHERE "solutions"."id" IN (SELECT "solutions"."id" FROM "solutions" WHERE "solutions"."id" = 121 ORDER BY id DESC)
SQL (0.1ms) UPDATE "solutions" SET "likes_count" = COALESCE("likes_count", 0) + 1 WHERE "solutions"."id" IN (SELECT "solutions"."id" FROM "solutions" WHERE "solutions"."id" = 121 ORDER BY id DESC)
Solution Load (0.3ms) SELECT "solutions".* FROM "solutions" WHERE "solutions"."id" = ? LIMIT 1 [["id", 121]]
I had the same problem. It turned out that my spec_helper.rb was loading the models a second time and therefore creating a second callback to update the counters. Make sure your Solution model isn't being reloaded by another process.
The answer above is also correct: you need to use == instead of be to do the comparison, but that will not fix the multiple updates that you are seeing in your log file.
You've the answer in your logs:
When you use be, it compares the object_id which is always the same for a couple of objects like true and 1. The id of 1 appears to be 2. Try in console: 1.object_id #=> 2
So replace your test with: solution.reload.likes_count.should eql 1 or even solution.reload.likes_count.should == 1