I retrieve a Post instance using find_by_id, set score on it, and call save. What I see is a really strange error where the SQL is missing the field in WHERE clause. Here's my rails console output:
me> rails c
Loading development environment (Rails 3.2.22.2)
2.1.5 :001 > post = Post.find_by_id 123456
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = 123456 LIMIT 1
=> #<Post id: 123456, url: "http://www.example.com", title: "Test Title", score: nil>
2.1.5 :002 > post.score = 1
=> 1
2.1.5 :003 > post.save
(0.1ms) begin transaction
(0.2ms) UPDATE "posts" SET "score" = 1 WHERE "posts"."" = 123456
(0.0ms) rollback transaction
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: posts.: UPDATE "posts" SET "score" = 1 WHERE "posts"."" = 123456
The problem was that my schema defined posts as
create_table "posts", :id => false, :force => true do |t|
t.integer "id", :limit => 8
...
This means Rails was instructed to not have a primary key column. All I had to do was add
set_primary_key :id
to my model.
Related
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)
I just noticed in my database that inside the Receipts table it is not showing the proper recipient_id for the receiver_id column. I am working between the Questions, Conversations and Receipts table with the Mailboxer gem.
For example here's the issue:
A new entry in the Questions table appears with the
id 552, sender_id 1, and recipient_id 2.
2 new entrys is then made in the Receipts table that's associated with the Questions entry just created (creates 2 entrys by default, one for recipient and other for sender). The details for the
first entry is id 547 and receiver_id 552.
The second entry id is 548 and receiver_id 1.
As you can see for the first entry the receiver_id is being copied from the Questions table id. It should be transferring the Questions table recipient_id instead.
I have no idea at how to fix this.
Questions controller:
def create
#question = Question.new(params[:question])
if #question.save
#message = current_user.send_message(#question, #question.question, "You have a question from #{#question.sender_id}")
redirect_to :back, notice: 'Your question was saved successfully. Thanks!'
else
render :new, alert: 'Sorry. There was a problem saving your question.'
end
end
end
Question model:
acts_as_messageable
attr_accessible :answer, :question, :sender_id, :recipient_id, :conversation_id
belongs_to :user
belongs_to :sender,:class_name => 'User',:foreign_key => 'sender_id'
belongs_to :recipient,:class_name => 'User',:foreign_key => 'recipient_id'
belongs_to :message
belongs_to :conversation
show.html.slim:
center
.message_div
= form_for Question.new, class: 'question_form form-horizontal', role: 'form' do |f|
.form-group
= f.text_field :question, {:placeholder => 'Please add your question...',class:'form-control'}
= f.hidden_field :sender_id, :value => current_user.id
= f.hidden_field :recipient_id, :value => #user.id
= f.submit 'Ask Question', class: 'btn btn-primary'
schema.rb:
create_table "receipts", force: true do |t|
t.integer "receiver_id"
t.string "receiver_type"
t.integer "notification_id", null: false
t.boolean "is_read", default: false
t.boolean "trashed", default: false
t.boolean "deleted", default: false
t.string "mailbox_type", limit: 25
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "receipts", ["notification_id"], name: "index_receipts_on_notification_id", using: :btree
add_foreign_key "receipts", "notifications", name: "receipts_on_notification_id"
Log:
Started POST "/questions" for 127.0.0.1 at 2014-08-08 17:00:12 -0400
Processing by QuestionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nYOcY69oD6WcfNGEJcKGpyupA0CNbYnAJz0SQ+dKtEk=", "question"=>{"question"=>"Stack Overflow can you help me out?", "sender_id"=>"3", "recipient_id"=>"2"}, "commit"=>"Ask Question"}
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`auth_token` = 'HkI7Lm4fJYHHf5LHEcMtvA' LIMIT 1
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `questions` (`created_at`, `question`, `recipient_id`, `sender_id`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'Stack Overflow can you help me out?', 2, 3, '2014-08-08 21:00:12')
(0.4ms) COMMIT
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 ORDER BY `users`.`id` ASC LIMIT 1
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO `conversations` (`created_at`, `subject`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'You have a question from 3', '2014-08-08 21:00:12')
SQL (0.3ms) INSERT INTO `notifications` (`attachment`, `body`, `conversation_id`, `created_at`, `sender_id`, `sender_type`, `subject`, `type`, `updated_at`) VALUES (NULL, 'Stack Overflow can you help me out?', 419, '2014-08-08 21:00:12', 3, 'User', 'You have a question from 3', 'Message', '2014-08-08 21:00:12')
SQL (0.3ms) INSERT INTO `receipts` (`created_at`, `mailbox_type`, `notification_id`, `receiver_id`, `receiver_type`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'inbox', 459, 577, 'Question', '2014-08-08 21:00:12')
(0.4ms) COMMIT
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO `receipts` (`created_at`, `is_read`, `mailbox_type`, `notification_id`, `receiver_id`, `receiver_type`, `updated_at`) VALUES ('2014-08-08 21:00:12', 1, 'sentbox', 459, 3, 'User', '2014-08-08 21:00:12')
(0.4ms) COMMIT
(0.1ms) BEGIN
(0.1ms) COMMIT
Redirected to http://localhost:3000/users/user
Completed 302 Found in 562ms (ActiveRecord: 62.3ms)
When you call send_message in your controller, you pass #question as a recipient. It means that the first receipt is indeed built with the question_id as receiver_id. But from what I understand, you want the question's recipient to be sent the message. Simply replace your send_message call with:
current_user.send_message(#question.recipient, #question.question, "You have a question from #{#question.sender_id}")
What's happening behind the scene
The Receipt table from mailboxer can be considered as a proof that a notification has been sent. In your model, by adding
acts_as_messageable
You include the Messageable module described in the mailboxer documentation, including the following send_message method:
def send_message(recipients, msg_body, subject, sanitize_text=true, attachment=nil, message_timestamp = Time.now)
# message = ...
# message building stuff happening
message.deliver false, sanitize_text
end
When you call that method from your controller, it calls the deliver method on this newly created message. Here's the interesting bits of that method, extracted from the mailboxer documentation:
def deliver(reply = false, should_clean = true)
# ... some stuff
#Receiver receipts
temp_receipts = recipients.map { |r| build_receipt(r, 'inbox') }
#Sender receipt
sender_receipt = build_receipt(sender, 'sentbox', true)
temp_receipts << sender_receipt
if temp_receipts.all?(&:save!)
# some other stuff
end
sender_receipt
end
As you can see temp_receipts = recipients.map { |r| build_receipt(r, 'inbox') } builds a receipt for every recipient of the message.
The second interesting line sender_receipt = build_receipt(sender, 'sentbox', true) builds a receipt for the sender. Which in your case is the current_user.
Hope it helps.
I have a model called Content, with a column called dependencies, serialized as Hash:
class Content < ActiveRecord::Base
attr_accessible :dependencies
serialize :dependencies, Hash
end
This really killed my nerves for the last few hours. I'll appreciate any help/hint.
Questions:
What should be the default (empty) value in migration?
What should be the default (empty) value in FactoryGirl?
Most important - how to query in order to find empty values?
Thanks in advance!
What should be the default (empty) value in migration?
What should be the default (empty) value in FactoryGirl?
In both cases, the empty hash {}
Most important - how to query in order to find empty values?
Since serialized values are stored using YAML, you need to search as follows:
Content.where('dependencies = ?', {}.to_yaml)
Here's an irb transcription for my test of the above:
MacbookAir1:so1 palfvin$ rails c
Loading development environment (Rails 4.0.0)
2.0.0-p247 :001 > u = User.new(role: {})
=> #<User id: nil, role: {}, role2: nil>
2.0.0-p247 :002 > u.save
(0.3ms) begin transaction
SQL (3.3ms) INSERT INTO "users" ("role", "role2") VALUES (?, ?) [["role", "--- {}\n"], ["role2", nil]]
(1.1ms) commit transaction
=> true
2.0.0-p247 :003 > u.role
=> {}
2.0.0-p247 :004 > {}.to_yaml
=> "--- {}\n"
2.0.0-p247 :005 > u
=> #<User id: 4, role: {}, role2: nil>
2.0.0-p247 :006 > User.where(role: {}.to_yaml)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."role" = '--- {}
'
=> #<ActiveRecord::Relation [#<User id: 3, role: {}, role2: nil>, #<User id: 4, role: {}, role2: nil>]>
2.0.0-p247 :007 >
(Note: I had created a User instance (#3) prior to posting the first version of this answer, which is why that shows up in my where as well).
And here's my user.rb file:
class User < ActiveRecord::Base
has_many :who_rated_comment_rels, foreign_key: "user_id", dependent: :destroy
serialize :role, Hash
serialize :role2
end
You can ignore the stuff not-relevant to your case (i.e. anything other than role). I hack on this project for various StackOverflow purposes.
Ok..I hope I am doing something dumb (which is usually the case). I have been humming along, adding cache counters to my existing project, when all of the sudden reset_counters failed for pretty much everything. I checked out an old copy when things were working, and it still failed, so I started on a brand new demo project.
I assume this is my error because it suddenly stopped working..and I don't see anyone else having similar issues.
I am using ruby 1.9.3-p392, and rails 3.2.12.
The counters themselves work on both existing and new projects. you just can't use reset_counters.
So here is the problem on a brand new project:
class Post < ActiveRecord::Base
attr_accessible :name
has_many :comments
end
class Comment < ActiveRecord::Base
attr_accessible :name
belongs_to :post, :counter_cache => true
end
here we can see the counters increment:
irb(main):073:0* post = Post.create(:name => 'i am a post')
(0.3ms) BEGIN
SQL (0.4ms) INSERT INTO `posts` (`comments_count`, `created_at`, `name`, `updated_at`) VALUES (0, '2013-03-14 19:56:53', 'i am a post', '2013-03-14 19:56:53')
(0.5ms) COMMIT
=> #<Post id: 3, name: "i am a post", created_at: "2013-03-14 19:56:53", updated_at: "2013-03-14 19:56:53", comments_count: 0>
irb(main):074:0> post.comments << Comment.new(:name => 'i am a comment')
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `comments` (`created_at`, `name`, `post_id`, `updated_at`) VALUES ('2013-03-14 19:57:18', 'i am a comment', 3, '2013-03-14 19:57:18')
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 3 LIMIT 1
SQL (0.3ms) UPDATE `posts` SET `comments_count` = COALESCE(`comments_count`, 0) + 1 WHERE `posts`.`id` = 3
(0.4ms) COMMIT
Comment Load (0.2ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 3
=> [#<Comment id: 3, name: "i am a comment", post_id: 3, created_at: "2013-03-14 19:57:18", updated_at: "2013-03-14 19:57:18">]
irb(main):075:0> post = Post.find(3)
Post Load (0.5ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 3 LIMIT 1
=> #<Post id: 3, name: "i am a post", created_at: "2013-03-14 19:56:53", updated_at: "2013-03-14 19:56:53", comments_count: 1>
running the code I get:
irb(main):001:0> Post.reset_counters(1,:comments_count)
Post Load (0.5ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
NoMethodError: undefined method `options' for nil:NilClass
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:22:in `block in reset_counters'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:19:in `each'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/activerecord-3.2.12/lib/active_record/counter_cache.rb:19:in `reset_counters'
from (irb):1
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands/console.rb:47:in `start'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands/console.rb:8:in `start'
from /Users/charles/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/railties-3.2.12/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
You're almost entirely correct. The parameter in reset_counters though should be the object name, not the column name. So in your case you want to:
1.9.3-p392 :005 > Post.reset_counters(1, :comments)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1 [["id", 1]]
(0.1ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1
(2.3ms) UPDATE "posts" SET "comments_count" = 1 WHERE "posts"."id" = 1
=> true