Reputation: 303
So two thirds of this works. Every time a User reads an Article, a History record is created (has_many through), which just says "User read Article at Read_Date_X".
The database is ok, the models are ok, the read_date param is permitted in the History controller, and the following operation works both 1) to check if a User has read an article before and 2) to create a new History record if it is the first time on this article.
But I cannot work out why the middle bit (to just update the read_date on an existing record) is not working. It doesn't matter if I try it with h.save! or h.update().
h = History.where(article_id: @article, user_id: current_user)
if h.exists?
h = History.where(article_id: @article, user_id: current_user)
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = @article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
The error it throws if it finds an existing record is:
undefined method `read_date=' for #<History::ActiveRecord_Relation:0x007fe7f30a5e50>
UPDATE: working answer
So Derek was right, and this version works. The middle bit needed a single instance, not an array, which is what the top conditional (without .first) was checking for. Using that to return a single record, though, means you need to swap "exists?" to "present?" in the second part.
h = History.where(article_id: @article, user_id: current_user).first
if h.present?
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = @article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
Upvotes: 0
Views: 1687
Reputation: 20263
I realize this question is already answered. Here are a couple of additional thoughts and suggestions.
I would not have a separate read_date
attribute. Just use updated_at
instead. It's already there for you. And, the way your code works, read_date
and updated_at
will always be (essentially) the same.
When looking up whether the history exists, you can do current_user.histories.where(article: @article)
. IMO, that seems cleaner than: History.where(article_id: @article, user_id: current_user).first
.
You can avoid all that exists?
and present?
business by just checking if the h
assignment was successful. Thus, if h = current_user.histories.where(article: @article)
.
If you go the route of using updated_at
instead of read_date
, then you can set updated_at
to Time.now
by simply doing h.touch
.
I would use the <<
method provided by has_many :through
(instead of building the history
record by hand). Again, if you use updated_at
instead of read_date
, then you can use this approach.
So, you could boil your code down to:
if h = current_user.histories.where(article: @article)
h.touch
else
current_user.articles << @article
end
You could use a ternary operator instead of that if then else
, in which case it might look something like:
current_user.histories.where(article: @article).tap do |h|
h ? h.touch : current_user.articles << @article
end
Upvotes: 1
Reputation: 2258
History.where(article_id: @article, user_id: current_user)
is returning a History::ActiveRecord_Relation
. If you want to set the read_date, you'll want to get a single record.
Here's one way you could do this with what you have currently:
h = History.where(article_id: @article, user_id: current_user).first
Another way you could handle this is by using find_by
instead of where
. This would return a single record. Like this:
h = History.find_by(article_id: @article, user_id: current_user)
However, if it's possible for a user to have many history records for an article, I would stick to the way you're doing things and make one change. If for some reason you have a lot of history records, this may not be very efficient though.
histories = History.where(article_id: @article, user_id: current_user)
histories.each { |history| history.update(read_date: Time.now) }
Upvotes: 1