How to find unrated models with acts_as_rateable - sql

I have a rails application using the acts_as_rateable plugin.
I'm stuck on figuring out how to retrieve unrated models using this plugin - however this is more of a general rails/SQL question than specific to this plugin.
Acts as rateable adds the following to the schema:
create_table "ratings", :force => true do |t|
t.integer "rating", :default => 0
t.string "rateable_type", :limit => 15, :default => "", :null => false
t.integer "rateable_id", :default => 0, :null => false
t.integer "user_id", :default => 0, :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "ratings", ["user_id"], :name => "fk_ratings_user"
And my rated models also have a user id column.
I'd like to be able to retrieve all instances of a particular model that haven't been rated at all, and also all instances that haven't been rated by someone other than the creator of the model, e.g. here is one model:
class Review < ActiveRecord::Base
acts_as_rateable
belongs_to :user
...
end
So I want something like the following pseudocode
Review.find(:all, :conditions=>"not rated by anyone")
Review.find(:all, :conditions=>"not rated by anyone except review.user")
However I can't figure out the SQL to do this, nor the rails magic to generate that SQL :-)
Update: this query seems to find all models that ARE rated by somebody other than the user that owns the model. So I think I just need to invert this somehow.
Review.find(:all,
:joins=>'left join ratings on reviews.id=ratings.rateable_id && ratings.rateable_type="Review"',
:conditions=>'reviews.user_id <> ratings.user_id',
:group=>'reviews.id')

Named scopes are the way to go for this problem. I would add two named scopes to your Review model. Something like:
class Review < ActiveRecord::Base
acts_as_rateable
belongs_to :user
named_scope :not_rated, :conditions => { :rating => 0 }
named_scope :not_rated_by_others,
:conditions => ["user != ? AND rating == 0", self.user]
end
Then you can do:
#not_rated = Review.not_rated
#not_rated_by_others = Review.not_rated_by_others
There's a Railscast that explains named scopes.
EDIT: Second Attempt
Right, let's have another go! One of the problems is that there are multiple acts_as_rateable plugins out there. I've been testing using this one on RubyForge.
class Review < ActiveRecord::Base
belongs_to :user
acts_as_rateable
named_scope :not_rated, :select => 'reviews.*',
:joins => 'left join ratings on reviews.id = ratings.rateable_id',
:conditions => 'ratings.rateable_id is null'
named_scope :not_rated_by_others, lambda { |user| { :select => 'reviews.*',
:joins => 'left join ratings on reviews.id = ratings.rateable_id',
:conditions => ['ratings.user_id != ? and ratings.rateable_id is null',
user] }}
end
Use it like this:
frank = User.find_by_name('frank')
#not_rated = Review.not_rated
#not_rated_by_others = Review.not_rated_by_others(frank)

I've got some of the way using a query like this:
Review.find(:all,
:joins=>'left outer join ratings on reviews.id=ratings.rateable_id && ratings.rateable_type="Review"',
:conditions=>'ratings.rating is NULL')
This looks like it returns all Review models with no rating at all.
And I think this one is working to find all Review models that aren't rated by the user who created the review:
Review.find(:all,
:joins=>'left outer join ratings on reviews.id=ratings.rateable_id && ratings.rateable_type="Review" && ratings.user_id <> reviews.user_id',
:conditions=>'ratings.rating is NULL')

Related

Find and count all Profiles with object name of HABTM relationship

In my Rails 3 app I have two models, Profile and Item. Each has a HABTM relationship with the other. In my Profile model, I have a method wish_items that creates an Array of that same name (wish_items). If an item contains the category "wish", it is added to the wish_items Array for that profile.
For the purpose of this question, say I have an item named "phone" with category "wish". What I'd like to do is be able to find and count all Profiles that have "phone" in their wish_items Array so I can render that count in a view. My code is below.
My Profile.rb model:
class Profile < ActiveRecord::Base
has_and_belongs_to_many :items
def wish_items
wish_items = Array.new
items.each do |item|
if item.category == "wish"
wish_items << item
end
end
return wish_items
end
end
My Item.rb model:
class Item < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
I have a join table items_profiles for this relationship. Here is that migration:
class CreateItemsProfiles < ActiveRecord::Migration
def self.up
create_table :items_profiles, :id =>false do |t|
t.references :item
t.references :profile
end
end
...
end
I saw this previous question and gave the answer a try but got an error NameError: uninitialized constant phone. Here is the code I tried from that:
Profile.all(:include => :items, :conditions => ["items.name = ?", phone])
Specifically I put that code in the following:
<%= pluralize(Profile.all(:include => :items, :conditions => ["items.name = ?", phone])).count, "person") %>
How can I do this?
The above was failing because I didn't have phone in quotations. Simple. The following worked:
Profile.all(:include => :items, :conditions => ["items.name = ?", "phone"])
And pluralizing:
<%= pluralize(Profile.all(:include => :items, :conditions => ["items.name = ?", "phone"]).count, "person") %>

Rails 3 uniqueness validation with scope on polymorphic table

I've got the following setup:
class Vote < ActiveRecord::Base
belongs_to :voteable, :polymorphic => :true, :counter_cache => true
end
class Proposition < ActiveRecord::Base
has_many :votes, :as => :voteable
end
class Winner < ActiveRecord::Base
has_many :votes, :as => :voteable
end
The Vote table looks like this:
t.string "ip_address"
t.integer "voteable_id"
t.string "voteable_type"
I want to validate the following. A user with a given ip_address can only vote on 1 proposition. So the combination of ip_address, voteable_id and voteable_type needs to be unique.
How can i achieve this with a "simple" validation rule?
To guarantee uniqueness you have to add unique index to your DB
If you don't have important data yet you can do it inside migration with add_index
add_index(:votes, [:ip_address, :voteable_id, voteable_type], :unique => true, :name => 'allowed_one_vote')
in case you already have some data it can be done with SQL and it depends on your DBMS
Add a scope to a unique :ip_address validation
class Vote < ActiveRecord::Base
# ...
validates :ip_address, uniqueness: { scope: [:voteable_type, :voteable_id]}
end

Rails 3.1 - before filter and database error

I'm having some trouble with lazy loading, i'm pretty sure of it ... maybe you could point out to me where I've failed.
def setup_guild
if params[:guild]
#guild = Guild.where(:short_name => params[:guild].upcase).first
if #guild.nil?
puts "no guild with short name #{params[:guild]} found"
redirect_to root_path
else
#title = t "layout.guild_title", :guild_name => (#guild.name).capitalize
end
else
#guild = nil
end
end
Which is called in ApplicationController as a before filter.
At first I used Guild.find_with_short_name, but I had the same dumb answer as now ... that is :
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
Which is, you'd guess the #title line up there.
The thing is, if I try something similar in the console I get the expected result
ruby-1.9.2-p0 > guild = Guild.where(:short_name => "ICPT").first
Guild Load (0.5ms) SELECT "guilds".* FROM "guilds" WHERE "guilds"."short_name" = 'ICPT' LIMIT 1
=> #<Guild id: 2, name: "Inception", detail: "Inception Guild", game_server_id: 2, created_at: "2011-10-30 17:41:19", updated_at: "2011-10-30 17:41:19", short_name: "ICPT">
ruby-1.9.2-p0 > guild.name.capitalize
=> "Inception"
More, if I put something like "puts #guild.inspect" right after the fetch, the capitalization works fine, hence I think it's lazy loading failure.
I'd be happy to have some idea as to how to solve that dumb problem ... I don't really want to have an #guild.inspect for nothing in my code, i find that to be lame solution ...
Thanks !
#PanayotisMatsinopoulos As requested, here is the table Guild :
create_table "guilds", :force => true do |t|
t.string "name"
t.text "detail"
t.integer "game_server_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "short_name"
end
#PanayotisMatsinopoulos Here you go my friend ;) I still have to i18n it
#encoding: utf-8
class Guild < ActiveRecord::Base
belongs_to :game_server
has_one :game, :through => :game_server
has_many :announcement, :dependent => :destroy
validates_presence_of :name, :on => :create, :message => "dois avoir un nom"
validates_presence_of :detail, :on => :create, :message => "dois avoir une description"
validates_presence_of :game, :on => :create, :message => "dois appartenir a un jeu"
validates_presence_of :short_name, :on => :create, :message => "dois avoir un acronyme"
validates_uniqueness_of :short_name, :on => :create, :message => "est deja utilise"
validates_length_of :short_name, :within => 3..5, :on => :create, :message => "dois faire entre 3 et 5 caracteres"
validates_exclusion_of :short_name, :in => %w( users admin guilds events loots sessions characters games password), :on => :create, :message => "ne peux pas utilisé se genre d'acronyme"
validates_uniqueness_of :name, :on => :create, :message => "est deja utilise"
has_many :guild_mates, :dependent => :destroy
has_many :guild_ranks, :dependent => :destroy
has_many :guild_settings, :dependent => :destroy
has_many :events, :dependent => :destroy
has_many :characters, :dependent => :destroy
before_validation :short_name_check ,:on => :create
after_create :guild_basic_settings
def guild_basic_settings
GuildSettingType.all.each do |grst|
grs = GuildSetting.create do |g|
g.guild_id = self.id
g.guild_setting_type_id = grst.id
g.value = "false"
end
end
set_setting(["setting_allow_basic_access_for_public","setting_allow_application",
"setting_allow_event_read_for_public","setting_allow_announcement_read_for_public"],"true")
end
def set_setting(setting,value)
if setting.class == Array
setting.uniq!
setting.each do |ar|
set_setting(ar,value)
end
else
grs = nil
if setting.class == String
grs = guild_settings.includes(:guild_setting_type).where(:guild_setting_type => {:name => setting}).first
return if grs.nil?
else
grs = guild_rank_settings.where(:guild_setting_type => setting)
return if grs.nil?
end
grs.value = value
grs.save
end
end
def short_name_check
short_name.upcase! if short_name
end
def full_name
"#{name.capitalize} - #{game_server.name}"
end
def characters_for_user(user)
characters.where(:user_id => user.id)
end
def method_missing(method,*args)
check = method.to_s.split("_")
if(args.count == 0)
if check[0] == "setting"
grs = guild_settings.joins(:guild_setting_type).where(:guild_setting => { :guild_setting_types => {:name => method.to_s}}).first
unless grs.nil?
return grs.value == "true" ? true : false
else
raise "Guild > Method Missing > unknown setting : #{method.to_s}"
end
end
end
end
end
Edit : I've just seen that i didn't super method missing ... might that be the problem ?
Okay it seems that the problem was my method_missing implementation. It was lacking super call ... Now that it has been restored, everything works fine. no wonder.
Thanks #PanayotisMatsinopoulos for your help :) (also thanks a good night of sleep ;p )
you should check if name is nil also:
if #guild.nil? || #guild.name.nil?
True. method_missing should call super at the end. But, I am not convinced that your problem is there. It may be. It may not.
On the other hand, let me tell something that I believe has more chances to be your problem. This is the fact that you carry out your validation on presence of name only :on => :create. This means that an update of an object Guild that does not contain the name will pass validation and will be saved in the database without problem. Then your setup_guild will definitely throw the error:
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
i.e. the error this discussion started about.
Hence, my suggestion is to remove your :on => :create condition on the validation of name. (an BTW...I suggest that you remove it from all your validations unless you know what you are doing)
But then, I cannot prove that this was your problem in the first place. I am just putting here my advice, rather than my positive answer as a solution to your problem.

Ruby on Rails model associations nubie question

dear developers I have some problems with Rails models
Here is my sql tables
create_table "areas", :primary_key => "ndc", :force => true do |t|
t.string "townname", :limit => 256, :null => false
end
create_table "books", :primary_key => "ndc", :force => true do |t|
t.integer "booked", :null => false
t.integer "free", :null => false
end
class Book < ActiveRecord::Base
self.primary_key = "ndc"
has_one :area, :foreign_key => 'ndc'
end
class Area < ActiveRecord::Base
self.primary_key = "ndc"
belongs_to :book , :foreign_key => 'ndc'
end
in controller I have such code
#books = Book.paginate :page => params[:page] || 1, :per_page => 10
#books.each do |booking|
p booking.area
p booking
end
In production mode doesn't work, booking.area is nil object. what it can be ?
Area becames nil if config.cache_classes = true
so booking.area generates such queries
if cashe_classes = true
SELECT areas.* FROM areas WHERE (areas.ndc = NULL) LIMIT 1
but without cashing classes
SELECT areas.* FROM areas WHERE (areas.ndc = 30) LIMIT 1
FIXED by removing belongs_to :book , :foreign_key => 'ndc' from area class.
Your areas table needs a book_id integer field to match against the books table's primary key.

Mixing has_one and has_and_belongs_to_many associations

I'm trying to build a database of urls(links). I have a Category model that has and belongs to many Links.
Here's the migration I ran:
class CreateLinksCategories < ActiveRecord::Migration
def self.up
create_table :links_categories, :id => false do |t|
t.references :link
t.references :category
end
end
def self.down
drop_table :links_categories
end
end
Here's the Link model:
class Link < ActiveRecord::Base
validates :path, :presence => true, :format => { :with => /^(#{URI::regexp(%w(http
https))})$|^$/ }
validates :name, :presence => true
has_one :category
end
Here's the category model:
class Category < ActiveRecord::Base
has_and_belongs_to_many :links
end
And here's the error the console kicked back when I tried to associate the first link with the first category:
>>link = Link.first
=> #<Link id: 1, path: "http://www.yahoo.com", created_at: "2011-01-10...
>>category = Category.first
=> #<category id : 1, name: "News Site", created_at: "2011-01-11...
>>link.category << category
=> ActiveRecord::StatementInvalid: SQLite3::Exception: no such column :
categories.link_id:
Are my associations wrong or am I missing something in the database? I expected it to find the links_categories table. Any help is appreciated.
Why are you using HABTM here at all? Just put a belongs_to :category on Link, and a has_many :links on Category. Then in the db, you don't need a join table at all, just a :category_id on the links table.
But, if you do want a HABTM here, from a quick glance, the first thing I noticed is that your join table is named incorrectly -- it should be alphabetical, categories_links.
The second thing is that you can't mix has_one and has_and_belongs_to_many. HABTM means just that -- A has many of B and A belongs to many of B; this relationship implies that the opposite must also be true -- B has many of A and B belongs to many of A. If links HABTM cateogries, then categories must HABTM links.
See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many