I have a count column in my tags table. I wanna increment tag count if a tag is just added to the post while updating, and it's already inside db. I added this to my post model:
before_update :increment_tag
def increment_tag
db_post = Post.find_by_id(self)
self.tags.each do |tag|
unless db_post.tags.include? tag
tag.update_attribute("count", tag.count + 1)
end
end
end
I get the post from db and test if the current tag is already in db, if it is, nothing happens, if it's not there, it should update count field. But for some reason this doesn't work.
You should not have a Count column in a tag. You should in stead set up your models propperly so you could do the following:
db_post.tags.count
If you do it right, you can get this in your tag:
tag.post.tags.count
If your aim is to find how many times the tag is used in a post, in total, you can simply count the instances in the TagToPostColumn (if you got one), which you need to sine this is a many-to-many relation.
Then you do:
TagToPostColumn.where(tag_id: someTag.id).count
count is a standard attribute, and you should never have to keep track of this yourself, unless you actually need a column called count that tracks something other than the models you have in the database. But then it is a good idea to name it something else than count, since it can lead to ambiguous attributes.
Also, i find it very strange that you are doing this:
db_post = Post.find_by_id(self)
Why are you not just using the self parameter, in stead of doing a db lookup to find the post you already have.
Related
I would like to order a collection first by priority and then due time like this:
#ods = Od.order(:priority, :due_date_time)
The problem is due_date_time is an instance method of Od, so I get
PG::UndefinedColumn: ERROR: column ods.due_date_time does not exist
I have tried the following, but it seems that by sorting and mapping ids, then finding them again with .where means the sort order is lost.
#ods = Od.where(id: (Od.all.sort {|a,b| a.due_date_time <=> b.due_date_time}.map(&:id))).order(:priority)
due_date_time calls a method from a child association:
def due_date_time
run.cut_off_time
end
run.cut_off_time is defined here:
def cut_off_time
(leave_date.beginning_of_day + route.cut_off_time_mins_since_midnight * 60)
end
I'm sure there is an easier way. Any help much appreciated! Thanks.
order from ActiveRecord similar to sort from ruby. So, Od.all.sort run iteration after the database query Od.all, run a new iteration map and then send a new database query. Also Od.all.sort has no sense because where select record when id included in ids but not searching a record for each id.
Easier do something like this:
Od.all.sort_by { |od| [od.priority, od.due_date_time] }
But that is a slow solution(ods table include 10k+ records). Prefer to save column to sort to the database. When that is not possible set logic to calculate due_date_time in a database query.
I would like to understand why in Rails 4 (4.2.0) I see the following behaviour when manipulating data in a join table:
student.student_courses
returns all associated records of courses for a given user;
but the following will save changes
student.student_courses[0].status = "attending"
student.student_courses[0].save
while this will not
student.student_courses.find(1).status = "attending"
student.student_courses.find(1).save
Why is that, why are those two working differently, is the first one the correct way to do it ?
student.student_courses[0] and student.student_courses.find(1) are subtly different things.
When you say student.student_courses, you're just building a query in an ActiveRecord::Relation. Once you do something to that query that requires a trip to the database, the data is retrieved. In your case, that something is calling [] or find. When you call []:
student.student_courses[0]
your student will execute the underlying query and stash all the student_courses somewhere. You can see this by looking at:
> student.student_courses[0].object_id
# and again...
> student.student_courses[0].object_id
# same number is printed twice
But if you call find, only one object is retrieved and a new one is retrieved each time:
> student.student_courses.find(1).object_id
# and again...
> student.student_courses.find(1).object_id
# two different numbers are seen
That means that this:
student.student_courses[0].status = "attending"
student.student_courses[0].save
is the same as saying:
c = student.student_courses[0]
c.status = "attending"
c.save
whereas this:
student.student_courses.find(1).status = "attending"
student.student_courses.find(1).save
is like this:
c1 = student.student_courses.find(1)
c1.status = "attending"
c2 = student.student_courses.find(1)
c2.save
When you use the find version, you're calling status= and save on entirely different objects and since nothing was actually changed in the one that you save, the save doesn't do anything useful.
student_courses is an ActiveRecord::Relation, basically a key => value store. The find method would only work on a model
I want to build functionality in my Rails application that shows follower trends over time.
Currently, my following methodology involves creating and destroying relationship objects - following creates an object with the IDs of the follower and followed and unfollowing deletes that relationship object.
Since the relationship object is deleted upon an unfollow, it's impossible to go back and look at how many followers existed for a followed at any given time.
To solve this, the best solution I can think of is this:
Instead of deleting a relationship object upon unfollowing, create a new object with a negative value of, say, -1. Following would create an object with a positive value of +1. Therefore, adding up the total values for a given pair would yield whether or not they were currently following (1 or 0), while historical trends could also be calculated by adding up the total following values for a given followed.
My question is: Is this the most elegant solution this problem? Is there an easier way to do it? I realize that it's possible to use cron jobs to output a daily number, but that seems like it would duplicate data. Any other suggestions?
Any help would be greatly appreciated!
I would add an active field then instead of deleting the relationship record I would set the record to inactive. Then you'll have to update all of your user facing queries to reflect active = 1. Then you can use the records with active = 0 for reporting purposes. You can also add a deactivated_at field that stores the date that the record was deactivated.
An example scenario would be user 1 follows user 2, follows user 3, follows user 4, un-follows user 2, re-follows user 2, un-follows user 4.
follower_id followed_id active created_at deactivated_at
1 2 0 9/10/2012 9/13/2012
1 3 1 9/10/2012 NULL
1 4 0 9/10/2012 9/17/2012
1 2 1 9/16/2012 NULL
just use paranoia
https://github.com/radar/paranoia
class Relationship < ActiveRecord::Base
acts_as_paranoid
...
end
(if you have a unique index over the two numeric ID columns, remove it, use a plain index)
Then you can have
def currently_following_count(uid)
Relationship.where(:followed_id => uid).count
end
def historical_following_count(uid)
Relationship.unscoped.where(:followed_id => uid).count
end
I am using the kaminari gem for pagination. I have a resources controller which paginates perfectly (due to the simple nature of the ordering). That can be seen here:
#resources = Resource.order("created_at desc").page(params[:page]).per(25)
That just sorts them by latest first. when i do .class it appears thats an activerecord::relation
On my tags though, I want to sort them by a relationship (the number of resources assigned to that tag)
#tags = Tag.all.sort{|a, b| b.number_of_resources <=> a.number_of_resources}.page(params[:page]).per(50)
It gives me the error however undefined methodpage' for #`
Tag.all returns an Array, hence your #page call failing, as it expects an ARel relation.
If #number_of_resources maps to a DB column, then all you need to do is:
Tag.order('number_of_resources').page(params[:page]).per(50)
If it's not, you either need to add it to the Tag database table, or just do your sort/paginate in Ruby rather than using kaminari. This will be feasible if the number of tags is under ~1000 or so.
If you do add the info to the db, check out this post: Counter Cache for a column with conditions?
you should do something like: 1) joins the two tables, 2) group rows by tag, 3) count how many rows belongs to each group, 4) order using that new column with the count
you should make a good sql statement and then you can call pagination
I have a model object which did not have a counter cache on it before and I added it via a migration. The thing is, I tried and failed to set the starting value of the counter cache based on the number of child objects I already had in the migration. Any attempt to update the cache value did not get written to the database. I even tried to do it from the console but it was never going to happen. Any attempt to write directly to that value on the parent was ignored.
Changing the number of children updated the counter cache (as it should), and removing the ":counter_cache => true" from the child would let me update the value on the parent. But that's cheating. I needed to be able to add the counter cache and then set its starting value to the number of children in the migration so I could then start with correct values for pages which would show it.
What's the correct way to do that so that ActiveRecord doesn't override me?
You want to use the update_counters method, this blog post has more details:
josh.the-owens.com add a counter cache to an existing db-table
This RailsCasts on the topic is also a good resource:
http://railscasts.com/episodes/23-counter-cache-column
The canonical way is to use reset_counter_cache, i.e.:
Author.find_each do |author|
Author.reset_counter_cache(author.id, :books)
end
...and that's how you should do it if those tables are of modest size, i. e. <= 1,000,000 rows.
BUT: for anything large this will take on the order of days, because it requires two queries for each row, and fully instantiates a model etc.
Here's a way to do it about 5 orders of magnitude faster:
Author
.joins(:books)
.select("authors.id, authors.books_count, count(books.id) as count")
.group("authors.id")
.having("authors.books_count != count(books.id)")
.pluck(:id, :books_count, "count(books.id)")
.each_with_index do |(author_id, old_count, fixed_count), index|
puts "at index %7i: fixed author id %7i, new books_count %4i, previous count %4i" % [index, author_id, fixed_count, old_count] if index % 1000 == 0
Author.update_counters(author_id, books_count: fixed_count - old_count)
end
It's also possible to do it directly in SQL using just a single query, but the above worked well enough for me. Note the somewhat convoluted way it uses the difference of the previous count to the correct one: this is necessary because update_counters doesn't allow setting an absolute value, but only to increase/decrease it. The column is otherwise marked readonly.