Tracking Followers Over Time - ruby-on-rails

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

Related

How to sum columns and group by date column in Rails

I have a model with following columns
Charges Model
Date
fee
discount
Data
1/1/15, 1, 1
1/1/15, 2, 1
2/2/15, 3, 3
I have a few named scopes like this_year
I want to do something like Charges.this_year.summed_up
How do I make a named scope for this.
The returned response then should be:
1/1/15, 3, 2
2/2/15, 3, 3
Assuming you have a model with a date field(eg. published_at) and 2 integer fields(eg. fee, discount). You can use "group" method to run GROUP BY on published_at. Then just use sum method if you want only sum of one fields. If you want more than one field, you have to run a select with SQL SUMs inside, to get multiple column sums. Here is an example.
Charge..group(published_at)
.select("published_at, SUM(fee) AS sum_fee, SUM(discount) AS sum_discount")
.order("published_at")
Note: Summarized fields won't show up in rails console return value prompt. But they are there for you to use.
Depending upon what end result you want, you may want to look at .group(:attribute) rather than .group_by:
Charge.group(:date).each do |charge|
charge.where('date = ?', charge.date).sum(:fee)
charge.where('date = ?', charge.date).sum(:discount)
end
I found this approach easier, especially if setting multiple conditions on the data you want to extract from the table.
In any case, I had an accounting model that presented this kind of issue where I needed credit and debit plus type of payment info on a single table and spent a fruitful few hours learning all about group_by before realizing that .group() offered a simple solution.

Creating a ruby object from multiple models

I am trying to create an object from several models which I can return using an active record query. The aim being to use the results to create a CSV output file from a subsequent query.
Lets say I have a simple model of items which have a status. The status is held with the ID as you would do in a normalised database. An item would have many statuses and a status would belong to an item (to achieve the relationship).
I've put some data in to illustrate this:
Item (model)
============
id status_id
---------------
1 1
2 2
3 3
Status
============
id title
---------------
1 Available
2 Loaned
3 Missing
Basically I want to be able to return the data as follows in an object:
item_id status_title
--------------------
1 Available
2 Loaned
3 Missing
I know I could achieve this using a "find by sql", however I was hoping this could be returned using active record and relationships.
I've had a look at trying to use delegate but couldn't get that to work (assuming that it would do what I need).
Anyone advise on if this is possible?
You should declare belongs_to association:
class Item
belongs_to :status
end
and use it in AR query:
Item.joins(:status).select('items.id as id, statuses.title as status_title')

How to create/save this database special calculated field - RoR

I have a database field called sample_code. This field is composed on the following way: sample_id/year/lab_id
The lab_id is fixed, always the same. The year changes accordingly to the current year... The sample_id its incremental (resetting every year and not a database field).
What I want to do is every-time I create a new sample it generates this sample_code for me and saves it along with the rest of the sample fields...
My doubts are:
1 - How can I keep incrementing sample_id if it's not on the database?
2 - How can I reset the sample ID code each year?
3 - Where's the best place to put/call this code? Sample's controller?
Thanks for all the help you can give
If you're not using your database outside of your app, it should be fine to just store the sample_id as its own column. You can then put a method on your model that returns something like:
def sample_code
"#{sample_id}/#{Time.now.year}/<lab_id>"
end
Then you can just increment sample_id each time.
EDIT
Since you need to reset the id to 1 each year and the model is called Sample, you should avoid confusion by calling it something like annual_id instead of sample_id. sample_id would likely be confused with sample.id, which is a completely different thing.
With that change, you should just store the info in three columns on the model to make it easy: annual_id, year, and lab_id. Then for each record you can set:
annual_id = Sample.where(year: Time.now.year).pluck(:annual_id).max.to_i + 1
year = Time.now.year
lab_id = <however you are defining this>
This will use the current year for year and then reset the annual_id to 1 when there are no records because the year has changed (new year will give nil.to_i + 1 => 1).
Then you can just return the format you want for any given data point:
def sample_code
"#{annual_id}/#{year}/#{lab_id}"
end

How to update attribute that is being validate for uniqueness

Table 'leave_policies' has year, increment and total_entitled fields.
I have written 'validates_uniqueness_of :year' in LeavePolicy model.
update_attributes not working even I am not updating year field.
Please guide for mistake or suggest any better solution.
Class LeavePolicy
validates_uniqueness_of :year
end
#leave_policy is object of LeavePolicy
In table a row with :id = 1 ,year = 1 , increment= 2 , total_entitled = 3. If I update row with id : 1 like
#leave_policy.update_attributes(:total_entitled => 5)
I got the error "year is already taken".
You are sure there is no other row with year of 1, and that the #leave_policy is actually an instance of the row with id 1?
If it were actually a new record, it would fail this way, or if there were any other record with :year == 1.
Validations are run on all attributes, even if you use update_attributes only on some, so if the year is not unique, it will fail even if you are only updating the increment or total_entitled fields.
In general, validates_uniqueness is not a great answer on its own, as race conditions from checking in ruby can prevent it from enforcing uniqueness, so I would also use it with a unique key in the database.
If you want to be able to skip the unique year validation when changing the other fields, you can set the attributes on the model, then call
leave_policy.save(:validate => false) #for rails 3
leave_policy.save(false) #for rails 2
and that will skip all validations, but man, I would think twice.
That seems to defeat the purpose, so I would instead pursue why it is that rails thinks you do not have a unique year - perhaps a look in your db will show there really is a dupe row for the year == 1.

How do you set the value of a newly added ActiveRecord counter cache?

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.

Resources