Adding integer to column in database - sql

I have an integer column on the Users table called rating_number. This number is going to consist of two things.
Impressions on page views
The total number of likes they have on their posts
So far, I have the impression part taken care of. I'm using the gem is_impressionable with a counter_cache like so on my User model:
is_impressionable :counter_cache => true, :column_name => :rating_number, :unique => :all
Now, I'm trying to add to that column the second part, which is the total number of votes they have on their posts. I am getting that integer by:
#user = current_user # or some user
array = #user.posts.map { |post| post.votes.count }
count = array.inject { |sum, x| sum + x }
where count is the total number of votes they have on their posts. How can I automatically update the rating_number column in an efficient way every time a User get's one of their posts voted_on. Should I instead go the direction where I manually add 1 to that column in the post's def vote action after the vote has successfully been saved?
Not sure if this is useful, but I'm also using the thumbs_up gem for voting system.

Lookig at the your need, I am quite sure you need to use callback called after_update in your User model. To understand how call back works, read Callbacks. But I would suggest you to keep the data in 2 separate columns, rather than a single column.
class User < ActiveRecord::Base
after_update :vote_update
# other methods
def vote_update
user = #post.user
user.rating_number = user.rating_number + 1
user.save!
end
end

Related

Get Users with an attribute for each Group?

My models: User, Group, and Membership. Users can have many groups through memberships, and vice-versa. I want to create a tool for website admins that produces a large table with the following specification:
Every row represents a user,
Every column represents a group,
In each cell of the table there is a boolean indicating whether the user belongs to the group.
What would be the best way to do this? Is it possible to write a single SQL query that achieves it (i.e. User.find_by_sql)? If not, how else?
p.s. I actually need a bit more than this (I need two columns per group, the first one indicating membership, and the second one counting how many meetings the user has attended in that group, but this involves the Meeting model, so I'll leave that for later.
Assuming that you're asking about the backend methodology not the data visualization aspect most of what JuanM. said is correct. One thing I would recommend is avoid writing his 'get_groups' method and just set up a 'has many through' relationship between users in groups. To do so put
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
end
In your Users model and vice versa in your Groups model (assuming memberships 'belongs_to' both). Then you'll have a '.groups' method on any User instance and a '.users' method on any Group instance
This would be my approach:
Write a function that returns if the user belongs to a group passed by parameter. Get all the groups from a user. In your user.rb model you can add this method get_groups to retrieve all groups from the user and then a method is_in(group). See code below:
class User < ActiveRecord::Base
#validations, some other stuff
has_many :memberships
#this method stores all the group-ids of the user in an array
def get_groups
myGroups = []
memberships.each do |membership|
membership.groups.each do |group|
myGroups << group.id
end
end
return myGroups
end
#this method receive a Group object and return true if the user belongs to that group
def is_in(group)
groups = get_groups
return groups.include?(group.id)
end
Then in your view or controller you can work as follow:
#the columns of the table
groups = Group.all
#iterating all users
User.all.each do |user|
#each user has to see if it belongs to each group in the groups table
groups.each do |group|
#the boolean value you display in a cell
value = user.is_in(group)
end
end

Paginating joined results with calculated columns

We are calculating statistics for our client. Statistics are calculated for each SpecialtyLevel, and each statistic can have a number of error flags (not to be confused with validation errors). Here are the relationships (all the classes below are nested inside multiple modules, which I have omitted here for simplicity):
class SpecialtyLevel < ActiveRecord::Base
has_many :stats,
:class_name =>"Specialties::Aggregate::Stat",
:foreign_key => "specialty_level_id"
.......
end
class Stat < Surveys::Stat
belongs_to :specialty_level
has_many :stat_flags,
:class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::StatFlag",
:foreign_key => "stat_id"
......
end
class StatFlag < Surveys::Stats::StatFlag
belongs_to :stat, :class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::Stat"
......
end
In the view, we display one row for each SpecialtyLevel, with one column for each Stat and another column indicating whether or not there are any error flags for that SpecialtyLevel. The client wants to be able to sort the table by the number of error flags. To achieve this, I've created a scope in the SpecialtyLevel class:
scope :with_flag_counts,
select("#{self.columns_with_table_name.join(', ')}, count(stat_flags.id) as stat_flags_count").
joins("INNER JOIN #{Specialties::Aggregate::Stat.table_name} stats on stats.specialty_level_id = #{self.table_name}.id
LEFT OUTER JOIN #{Specialties::Aggregate::StatFlag.table_name} stat_flags on stat_flags.stat_id = stats.id"
).
group(self.columns_with_table_name.join(', '))
Now each row returned from the database will have a stat_flags_count field that I can sort by. This works fine, but I run into a problem when I try to paginate using this code:
def always_show_results_count_will_paginate objects, options = {}
if objects.total_entries <= objects.per_page
content_tag(:div, content_tag(:span, "Showing 0-#{objects.total_entries} of #{objects.total_entries}", :class => 'info-text'))
else
sc_will_paginate objects, options = {}
end
end
For some reason, objects.total_entries returns 1. It seems that something in my scope causes Rails to do some really funky stuff with the result set that it gives me.
The question is, is there another method I can use to return the correct value? Or is there a way that I can adjust my scope to prevent this meddling from occurring?
The group statement makes me suspicious. You may want to fire up a debugger and step through the code and see what's actually getting returned.
Is there a special reason you're using a scope and not just an attribute on the SpecialtyLevel model? Couldn't you just add a def on SpecialtyLevel that would function as a "virtual attribute" that just returns the length of the list of StatFlags?
The answer here is to calculate total_entries separately and pass that into the paginate method, for example:
count = SpecialtyLevel.for_participant(#participant).count
#models = SpecialtyLevel.
with_flag_counts.
for_participant(#participant).
paginate(:per_page => 10, :page => page, :total_entries => count)

Getting previous HABTM values

In my app, I have several clients, and they have several elements (via has_many_through association) depending on a certain BusinessType to which Client belongs to so that instead of manually adding all the elements to the Client, I can just select the BusinessType and everything gets added automatically (business_type in Client is attr_readonly). BusinessType HABTM elements.
Here's the catch, after creation with the default BusinessType, the clients can update their elements and remove or add as they please (mostly add), so what I'm trying to do is the following:
Suppose one business_type has elements [1,2,3] and is assigned to one client, then, the following elements are added manually to the client = [4,5,6] so it ends up having [1,2,3,4,5,6], ok everything's fine here.
But after this, the business_type gets updated and has element 2 removed, so it ends up being [1,3]. Here's the deal, I want the client to be updated by removing the 2, but not the [4,5,6] that do not correspond to the business_type in question so that it ends up [1,3,4,5,6], I'm using an after_update callback to update the clients' elements but the _was method doesn't work for HABTM relationships (to get the old business_type's elements.
I've tried using a before_update callback to first to client.elements = client.elements - business_type.elements to store momentarily in the DB [1,2,3,4,5,6] - [1,2,3] = [4,5,6], and in the after_update do client.elements = client.elements + business_type.elements to get [4,5,6] + [1,3] = [1,3,4,5,6]but this has already the new value of [1,3]. How can I get the old business_type.elements value in the before_update or after_update?
Thanks in advance for your help!
I had a similar problem in an app, and the only solution I could come up with was to store the values before doing update_attributes in the controller.
Example code:
Models
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_products"
def remember_prev_values(values)
#prev_values = values
end
def before_update_do_something
puts #prev_values - self.category_ids # Any categories removed?
puts self.category_ids - #prev_values # Any categories added?
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products, :join_table => "categories_products"
end
In the update method in the products controller I do the following:
class ProductsController < ApplicationController
...
def update
#product.remember_prev_values(#product.category_ids)
if #product.update_attributes(params[:product])
flash[:notice] = "Product was successfully updated."
redirect_to(product_path(#product))
else
render :action => "edit"
end
end
...
end
It is not ideal, but it is then possible to "catch" the habtm inserts/removes before they are executed.
I do think it is possible to do in a callback, but you might need to "hack" into ActiveRecord.
I did not spend much time on trying to dig into ActiveRecord internals, as this is a simple implementation that works.
You should use after_initialize callback to store previous values.
after_initialize do #previous_elements = elements.map{|x| x} end
Note that here we make a copy of assosiations by map function call.

Rails: complex search on 3 models, return only newest - how to do this?

I'm trying to add an advanced search option to my app in which the user can search for certain links based on attributes from 3 different models.
My app is set up so that a User has_many :websites, Website has_many :links, and Link has_many :stats
I know how create SQL queries with joins or includes etc in Rails but I'm getting stuck since I only want to retrieve the latest stat for each link and not all of them - and I don't know the most efficient way to do this.
So for example, let's say a user has 2 websites, each with 10 links, and each link has 100 stats, that's 2,022 objects total, but I only want to search through 42 objects (only 1 stat per link).
Once I get only those 42 objects in a database query I can add .where("attribute like ?", user_input) and return the correct links.
Update
I've tried adding the following to my Link model:
has_many :stats, dependent: :destroy
has_many :one_stat, class_name: "Stat", order: "id ASC", limit: 1
But this doesn't seem to work, for example if I do:
#links = Link.includes(:one_stat).all
#links.each do |l|
puts l.one_stat.size
end
Instead of getting 1, 1, 1... I get the number of all the stats: 125, 40, 76....
Can I use the limit option to get the results I want or does it not work that way?
2nd Update
I've updated my code according to Erez's advice, but still not working properly:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat.indexed
end
=> true
=> true
=> true
=> false
=> true
=> true
=> true
Link.includes(:latest_stat).where("stats.indexed = ?", false).count
=> 6
Link.includes(:latest_stat).where("stats.indexed = ?", true).count
=> 7
It should return 1 and 6, but it's still checking all the stats rather than the latest only.
Sometimes, you gotta break through the AR abstraction and get your SQL on. Just a tiny bit.
Let's assume you have really simple relationships: Website has_many :links, and Link belongs_to :website and has_many :stats, and Stat belongs_to :link. No denormalization anywhere. Now, you want to build a query that finds, all of their links, and, for each link, the latest stat, but only for stats with some property (or it could be websites with some property or links with some property).
Untested, but something like:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where("stats.id = (select max(stats2.id)
from stats stats2 where stats2.link_id = links.id)")
That last bit subselects stats that are part of each link and finds the max id. It then filters out stats (from the join at the top) that don't match that max id. The query returns websites, which each have some number of links, and each link has just one stat in its stats collection.
Some extra info
I originally wrote this answer in terms of window functions, which turned out to be overkill, but I think I should cover it here anyway, since, well, fun. You'll note that the aggregate function trick we used above only works because we're determining which stat to use based on its ID, which exactly the property we need to filter the stats from the join by. But let's say you wanted only the first stat as ranked by some criteria other than ID, such as as, say, number_of_clicks; that trick won't work anymore because the aggregation loses track of the IDs. That's where window functions come in.
Again, totally untested:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where(
"(stats.id, 1) in (
select id, row_number()
over (partition by stats2.id order by stats2.number_of_clicks DESC)
from stat stats2 where stats2.link_id = links.id
)"
)
That last where subselects stats that match each link and order them by number_of_clicks ascending, then the in part matches it to a stat from the join. Note that window queries aren't portable to other database platforms. You could also use this technique to solve the original problem you posed (just swap stats2.id for stats2.number_of_clicks); it could conceivably perform better, and is advocated by this blog post.
I'd try this:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat
end
Note you can't print latest_stat.size since it is the stat object itself and not a relation.
Is this what you're looking for?
#user.websites.map { |site| site.links.map { |link| link.stats.last } }.flatten
For a given user, this will return an array with that contains the last stats for the links on that users website.

Rails Unique Order Field For Create and Update Operations

Edit:
Is it possible to create a unique auto increment field that will be incremented on creates and updates in SQL using Rails (similar to an id field but incremented and re-assigned after an update)? For example:
Create Record A (Value: 1)
Create Record B (Value: 2)
Update Record A (Value: 3)
Update Record B (Value: 4)
I'm trying to setup pull synchronization and need a way to grab all records that have been created or updated since a previous synchronization.
I initially used the 'created_at' and 'updated_at' fields, but found them to be difficult to work with and somewhat inaccurate for partial synchronizations.
Edit:
I'm using Postgresql and Sqlite as my databases, so hopefully a solution exists that will work for both systems.
Edit:
To clarify, I want to pass a single integer to my server from the client (the largest 'sync' integer) and get back all the records created or updated after that record was created or updated.
Ended up adding a sequence integer field to my model and setup the following migration:
class CreateSequence < ActiveRecord::Migration
def self.up
begin
execute "CREATE SEQUENCE sequence"
rescue
end
end
def self.down
begin
execute "DROP SEQUENCE sequence"
rescue
end
end
end
Then, in my model I added:
before_save do
self.sequence = self.class.sequence
end
def self.sequence
s ||= self.connection.select_value("SELECT nextval('sequence') ") rescue nil
s ||= self.connection.select_value("SELECT strftime('%s','now')") rescue nil
return
end
Note: For Sqlite sequences are not supported so instead a selection of an 'epoch' form the database is required. However, this has the negative side effect of causing the sequence to be non-unique for rapid creation. However, in my case this was not an issue.
You could use a before_save callback, like so:
class MyModel < ActiveRecord::Base
before_save :increment
...
protected
def increment
self.revision ||= 1
self.revision += 1
end
end
You could make this more reusable by defining and using a Callback Class.
Another option it to use a Gem/plugin that does automatic versioning (and thus maintains a version field).
Whoops, didn't read too carefully.
If you want to grab the records that have been modified since the last sync you could create a boolean field for determining if the current record was synced. Set it to false by default and set it to false on any edit. That should allow you to pull only the items you need.
There is an id attribute that is provided by default and it auto increments. By default it is an integer, however if you were looking for a guid then let me know and I can point you to some good resources.
As far as pulling records since the last sync you could just grab the last id when you run the sync and use it as a starting value when you sync again.
So... the serial number is not associated with a row so much as a table, right?
class SerialNumber < AR::Base
has_many :thingies
# just has an integer serial number field
end
class Thingie < AR::Base
belongs_to :serial_number # probably want to include this in default scope
before_create :bump_serial
before_save : bump_serial
private
def bump_serial
self.serial_number ||= 0
self.serial_number += 1
end
end
This would appear to handle the cases of create, new/save, and update. But not destroy.
Try using the act_as_versioned gem.
It sets a version field for each record that you could use for synchronization. And I think that this would be a better way to synchronize across clients since you can compare the version on the server and client and synchronize those that are higher on the server.
The docs are here.
And the rubygem page is here.