So I have two models - Status and Post.
A status can either be: Empty, Half, or Full.
Each post can only have 1 status at any one time, i.e. a post can only be either empty or half, etc. It has to be one of the 3.
However, a status can have many posts. So the Empty status may have 20 posts.
What is the best way to approach this, from an associations perspective?
I was initially thinking that a Post has_one Status. But the issue with that, is that the Status would have to belong_to a Post.
So right now, I have it as:
Status has_many :posts
Post belongs_to :status, counter_cache: true
But whenever I want to assign a status to a post, I have to do it backways and it feels weird.
i.e. I have to do something like:
> g = Post.second
Post Load (0.7ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT 1 OFFSET 1
=> #<Post id: 19, title: "10PP gives you 1 on 1", photo: "1-on-1-Icon.jpg", body: "10PP gives you the real one on one attention you c...", created_at: "2014-08-30 10:48:18", updated_at: "2014-08-30 10:48:18", user_id: 1, ancestry: nil, file: nil, status_id: nil>
2.1.1p76 :010 > half = Status.second
Status Load (0.5ms) SELECT "statuses".* FROM "statuses" ORDER BY "statuses"."id" ASC LIMIT 1 OFFSET 1
=> #<Status id: 2, name: "half", created_at: "2014-08-28 08:04:42", updated_at: "2014-08-28 08:04:42", posts_count: nil>
2.1.1p76 :011 > half.posts << g
(0.1ms) BEGIN
SQL (0.3ms) UPDATE "posts" SET "status_id" = $1, "updated_at" = $2 WHERE "posts"."id" = 19 [["status_id", 2], ["updated_at", "2014-08-30 11:37:16.121245"]]
SQL (0.4ms) UPDATE "statuses" SET "posts_count" = COALESCE("posts_count", 0) + 1 WHERE "statuses"."id" = 2
(0.9ms) COMMIT
Post Load (0.8ms) SELECT "posts".* FROM "posts" WHERE "posts"."status_id" = $1 [["status_id", 2]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 19, title: "10PP gives you 1 on 1", photo: "1-on-1-Icon.jpg", body: "10PP gives you the real one on one attention you c...", created_at: "2014-08-30 10:48:18", updated_at: "2014-08-30 11:37:16", user_id: 1, ancestry: nil, file: nil, status_id: 2>]>
I would rather be going the other way, i.e. assigning a status to the post.
I feel like this is not the best way to do it, but not sure how else to tackle it.
Also, how would I tackle the PostsController#Create?
Right now, I have just:
#post = current_user.posts.new(post_params)
Which doesn't assign the correct Status object to the current post.
you could try other way with using enum http://edgeguides.rubyonrails.org/4_1_release_notes.html#active-record-enums
class Post < ActiveRecord::Base
enum status: [ :empty, :half, :full ]
end
Post.full
Post.first.empty?
Related
I have a post that has many comments. Comments have a body and a title
=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, author: "jack", body: "how do you like dem apples?", post_id: 1, created_at: "2016-09-29 02:11:00", updated_at: "2016-09-29 02:11:00">]>
2.3.0 :005 > Post.first.comments
Post Load (0.5ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT 1
Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, author: "jack", body: "how do you like dem apples?", post_id: 1, created_at: "2016-09-29 02:11:00", updated_at: "2016-09-29 02:11:00">]>
2.3.0 :006 > Post.first.comments.body
NoMethodError: Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]]
undefined method `body' for #<Comment::ActiveRecord_Associations_CollectionProxy:0x007f9bef0a33a8>
In the code above you can see that I try to get the body attribute from the post that has a comment, but I get a no method exception. How do I retrieve the associated objects data in these types of situations?
1) You get error because you are calling body on the collection of comments, not a single instance of Comment class.
2) To get it working:
# select the comment, which's body you want to get
Post.first.comments.first.body
Post.first.comments is a collection, you can treat it as an array and map it, for example, to get all comments' bodies:
# would return all bodies of all comments, that belongs to the `Post.first`
Post.first.comments.pluck(:body)
Always read exceptions messages carefully.
In my Rails app I have invoices and their associated items.
In my InvoicesController I added this method:
def duplicate
invoice = Invoice.find(params[:id])
#invoice = invoice.dup
invoice.items.each do |item|
#invoice.items << item
end
render :new
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Clicking the link instantiates a form with the correct invoice and items data.
However, when trying to Create the record I get an error:
Couldn't find Item with ID=4 for Invoice with ID=
Can anybody tell me what I am missing here or if there's a smarter way to duplicate a record including its associated records?
Thanks for any help.
Here's cose that will duplicate the main object, and each of the items underneath it. The new object will not be saved, nor will the items (yet).
def duplicate
dup.tap do |new_invoice|
items.each do |item|
new_invoice.items.push item.dup
end
end
end
And a quick test to prove the things:
require 'test_helper'
class InvoiceTest < ActiveSupport::TestCase
def original_invoice
Invoice.create(number: 5).tap do |invoice|
invoice.items.create(name: "a", price: "5")
invoice.items.create(name: "b", price: "50")
end
end
test "duplication" do
new_invoice = original_invoice.duplicate
new_invoice.save
assert_equal 2, new_invoice.items.count
end
end
Let's deconstruct this a little bit, starting with your error message.
Couldn't find Item with ID=4 for Invoice with ID=
Now, at first thought, one might be tempted to consider that there is no Item with ID 4. It's a good sanity check to make sure that simple things like this aren't the issue. In this case, we have the appropriate item, so we can move on.
What struck me as odd at first was the lack of a number following ID=. It turns out that this hints as to what the problem is.
Let's have a look at some console output. I will be using Cat objects, simply because they are awesome.
The first thing we want to do is to get a Cat:
Cat.first
=> Cat Load (0.2ms) SELECT "cats".* FROM "cats" LIMIT 1
=> #<Cat id: 2, age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: "2013-06-08 21:44:22", updated_at: "2013-06-08 21:44:22", user_id: 1>
After we have the cat, let's duplicate it.
Cat.first.dup
Cat Load (0.3ms) SELECT "cats".* FROM "cats" LIMIT 1
=> #<Cat age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: nil, updated_at: nil, user_id: 1>
What do we notice about the duplicated cat? Well, for starters, both created_at and updated_at are nil. This is usually a sign that the object hasn't be saved to the database. If we go to look for the Cat's ID, we notice that there isn't even a column for that!
Let's try saving the new object.
Cat.first.dup.save
Cat Load (0.3ms) SELECT "cats".* FROM "cats" ORDER BY "cats"."id" DESC LIMIT 1
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "cats" ("age", "birthdate", "color", "created_at", "gender", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?) [["age", 6], ["birthdate", Sat, 08 Jun 2013], ["color", "brown"], ["created_at", Sun, 09 Jun 2013 18:10:47 UTC +00:00], ["gender", "m"], ["name", "Aaron"], ["updated_at", Sun, 09 Jun 2013 18:10:47 UTC +00:00], ["user_id", 1]]
(0.7ms) commit transaction
=> true
Now, if we call Cat.last, it will return this object.
Cat.last
Cat Load (0.3ms) SELECT "cats".* FROM "cats" ORDER BY "cats"."id" DESC LIMIT 1
=> #<Cat id: 11, age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: "2013-06-09 18:10:47", updated_at: "2013-06-09 18:10:47", user_id: 1>
Your problem is that, while you are duplicating the Invoice, you aren't saving it to the database. Couldn't find Item with ID=4 for Invoice with ID= confirms this as it tells us the duplicated Invoice has no ID, which would only be the case if it had not been saved.
Cheers!
I have a model with a dep_date field, which is a date (not datetime). When querying the model for records with a given date, nothing is returned. Using RoR 3.2 with SQLite
1.9.3-p327 :019 > Flight.find(62)
Flight Load (0.4ms) SELECT "flights".* FROM "flights" WHERE "flights"."id" = ? LIMIT 1 [["id", 62]]
=> #<Flight id: 62, dep_city_id: 3, arr_city_id: 15, dep_date: "2013-03-28", dep_time: nil, price_per_adult: #<BigDecimal:b7e3c94,'-0.0',9(9)>, price_per_child: #<BigDecimal:b7e3c30,'-0.0',9(9)>, free_seats: nil, flight_status: nil, created_at: "2013-03-14 22:15:48", updated_at: "2013-03-14 22:15:48", arr_date: "2013-04-15", arr_time: nil, c_flight_time: 0, c_flight_distance: 0>
1.9.3-p327 :020 > Flight.where(:dep_date => "2013-03-28")
Flight Load (0.5ms) SELECT "flights".* FROM "flights" WHERE "flights"."dep_date" = '2013-03-28'
=> []
My first guess would be that Rails is interrupting "2013-03-28" as a string and not as a date. I would suggest trying this:
Flight.where(:dep_date => "2013-03-28".to_date)
This was caused by SQLite3. Querying on dates works fine with PostgreSQL.
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
[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.