Rails find with three tables and a SUM operation - sql

I'm a little stumped as to get the order of records I want with a find operation.
Let's say you had three models:
1. Websites
2. Links
3. Votes
A website has many links and a link has many votes. Each vote has a certain amount of points that a user can attribute to that vote. I'm trying to get a website index page where websites are listed in order of the sum of the points they've received for all the links for that website.
Here's a simplified version of the schema
create_table "votes", :force => true do |t|
t.integer "link_id"
t.integer "points"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
create_table "links", :force => true do |t|
t.string "name"
t.string "link"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "votes_count", :default => 0
t.integer "website_id"
end
create_table "websites", :force => true do |t|
t.string "domain"
t.boolean "verified", :default => false
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
I'm trying to think about the right active record query to use here. Any help would be appreciated.

To think that I spent a day pulling my hair out when I came up with a solution just an hour after I posted this question.
In the website model I put that the website has_many :points, through => :links
Then the query:
array = Website.find(:all)
array.sort_by {|w| w.donations.sum('amount')}.reverse
This seems to work.

Related

SQL Query in two different Rails models that have no association

I have a landlords table, and landlord_addresses table, and a landlord_companies table. From the landlords index view and using the landlords_controller I need to be able to search the landlord_companies, BUT landlords and landlord_companies have no ActiveRecord association with each other. Obviously I can't use what I've written below, but I am not sure how to search the landlord_companies ...any help would be great!
#landlord = #landlords.includes(:landlord_company)
.references(:landlord_companies)
.where("landlord_companies.llc_name LIKE ?", "%#{params[:landlord_llc_search]}%")
Schema:
create_table "landlords", force: :cascade do |t|
t.string "name"
t.string "contact_name"
t.string "contact_number"
t.integer "listing_agent_id"
t.boolean "own_application"
t.boolean "own_credit"
t.boolean "accepts_roommate_matchups"
t.boolean "management_company"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "landlord_companies", force: :cascade do |t|
t.string "llc_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "landlord_address_id"
end
I was able to figure this out and actually use the SQL query I had already written. I thought there was no association, but technically there was using a through.
Landlord.rb
has_many :landlord_companies, through: :landlord_addresses

Most efficient way to get a Users Tags

I have a somewhat complicated database structure in a Ruby on Rails project. A User can have many EmailAccounts, an EmailAccount can have many Subscriptions (many-to-many via a join table), and a Subscription can have many Tags (also many-to-many)
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.date "dob"
t.string "gender"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "email_accounts", force: :cascade do |t|
t.integer "user_id"
t.string "email"
t.string "provider"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "account_subscriptions", force: :cascade do |t|
t.integer "email_account_id", null: false
t.integer "subscription_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "subscriptions", force: :cascade do |t|
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
t.string "domain"
end
create_table "sub_tags", force: :cascade do |t|
t.integer "subscription_id", null: false
t.integer "tag_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
I want to see which Tags apply to a User through this relationship. Whats the most efficient way to do so (eager load all the relationships and loop through them, do a multi-level join in Postgres, etc)?
Assuming you have the right models
user = User.find(user_id)
tags = Tag.where(:id => SubTag.where(:subscription_id => AccountSubscription.where(:email_account_id => user.email_accounts.map{|e| e.id}).map{|a_s| a_s.subscription_id}).map{|s_t| s_t.tag_id})
tag_name = tags.map{|t| t.name}
this should work fine given you have the right indexes.

Converting a SQL query to Active Record in Rails 4

Here are my two models
class Comment < ActiveRecord::Base
belongs_to :phone
end
class Phone < ActiveRecord::Base
has_many :comments, :dependent => :destroy
end
Here is the schema for the tables
ActiveRecord::Schema.define(version: 20131119231249) do
create_table "comments", force: true do |t|
t.string "username"
t.string "ipaddy"
t.text "pcomments"
t.string "company"
t.string "calltype"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "pnumber"
t.string "source"
end
create_table "phones", force: true do |t|
t.integer "pnumber"
t.text "mrcomment"
t.integer "ccount"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Here is the raw SQL that works
SELECT phones.ccount ,
comments.*
FROM phones
INNER JOIN comments
ON phones.pnumber = comments.pnumber;
When I run the following in my controller
#phones = Phone.select("phones.ccount, comments.*").joins(:comments).where(:comments => {comments.pnumber => phones.pnumber})
I get the following error
undefined local variable or method `comments' for #<FrontPageController:0x00000003c56c70>
Any help on what the active record statement should like would be greatly appreciated
It seems like you're using the select() erroneously. Have you read the docs: http://apidock.com/rails/ActiveRecord/QueryMethods/select ?
from docs: "Second: Modifies the SELECT statement for the query so that only certain fields are retrieved:"
The query's syntax should more look like (using a standard example):
l = Location.where(["id = ?", id]).select("name, website, city").first.

ActiveRecord Reputation System related queries (has_many_through + polymorphic associations)

Problem
I'm building an application (Rails 4, mysql) to facilitate innovation tournaments, similar to the Darwinator. I'm using ActiveRecord Reputation System as the rating system. I need to a JSON of the following format
results =
{
contributor_count: 51 /* (submitters UNION raters).count */
opportunity_count: 100 /* total ideas submitted */
vote_count: 250 /* total votes/ratings cast */
opportunities: [ /* descending order of avg_rating */
{avg_rating: 9.6, content: "vision"},
{avg_rating: 9, content: "favorite xkcd comic"},
{avg_rating: 8, content: "smash bro skill"},
{avg_rating: 7.9, content: "programming proficiency"},
{avg_rating: 7.1, content: "experience"},
...
]
}
User Story
Bob creates/starts the following tournament:
"What is the most important characteristic in a CTO?"
Bob convinces 20 of his friends to submit a total of 100 ideas (opportunities):
"vision", "favorite xkcd comic", "experience", "smash bro skill",
"programming proficiency", etc.
Bob then convinces 50 of his friends (including most of the idea submitters) to help him filter through the submissions (opportunities) by rating the submissions on a 1 - 10 scale.
After 250 ratings have been performed on the 100 submitted opportunites (average of 2.5 ratings per opportunity), Bob clicks the "Finish Tournament" button, and is taken to the tournament result page
Bob is presented with the results of the tournament (see JSON above)
Problems to be Solved
Result JSON:
contributor_count
opportunity_count
vote_count
opportunities in descending order of avg_rating
Rating Process
Randomly select an opportunity that the user has not rated/voted/evaluated
voted_on_count
remaining_to_vote_on_count
Status / Updates
I will be updating this throughout my process of solving this problem. Look forward to additional information.
contributor_count:
I can get submitters through #tournaments.users. I need to be able to the unique set of raters, then UNION the submitters with the raters... How can I get the unique set of raters?
opportunity_count:
Freebie. #tournament.opportunities.count
vote_count:
Current strategy is to #tournament.opportunities.inject(0) {|sum, o| sum = sum + o.evaluators_for(:avg_ratings).count }
opportunities in descending order of avg_rating:
Hmmm... some raw sql?
Randomly select an opportunity that the user has not rated/voted/evaluated:
Using the ramdumb gem, #opportunity = #tournament.opportunities.random... but, need to filter out those that the user has not rated
voted_on_count:
Related to the previous problem. #tournament.opportunities.where(has voted on this opportunity).count
remaining_to_vote_on_count:
#tournament.opportunities.count - voted_on_count
Models
class Tournament < ActiveRecord::Base
has_many :opportunities
# submitters of
has_many :users, through: :opportunities
...
end
class Opportunity < ActiveRecord::Base
belongs_to :tournament
belongs_to :user
has_reputation :avg_rating,
source: :user,
aggregated_by: :average
...
end
class User < ActiveRecord::Base
has_many :opportunities
has_many :tournaments, through: :opportunities
has_many :evaluations, class_name: "RSEvaluation", as: :source
has_reputation :avg_rating,
source: {reputation: :avg_rating, of: :opportunities},
aggregated_by: :average
...
end
schema.rb
create_table "tournaments", force: true do |t|
t.boolean "currently_running", default: true
t.datetime "created_at"
t.datetime "updated_at"
t.string "description"
end
create_table "users", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "opportunities", force: true do |t|
t.string "content"
t.integer "tournament_id"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "rs_evaluations", force: true do |t|
t.string "reputation_name"
t.integer "source_id"
t.string "source_type"
t.integer "target_id"
t.string "target_type"
t.float "value", default: 0.0
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "rs_reputations", force: true do |t|
t.string "reputation_name"
t.float "value", default: 0.0
t.string "aggregated_by"
t.integer "target_id"
t.string "target_type"
t.boolean "active", default: true
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "rs_reputation_messages", force: true do |t|
t.integer "sender_id"
t.string "sender_type"
t.integer "receiver_id"
t.float "weight", default: 1.0
t.datetime "created_at"
t.datetime "updated_at"
end

ActsAsTaggableOn after own tag implementation

I had my own implementation of tags in my rails application and wanted to replace it by the ActsAsTaggableOn gem. I generated the migration and deleted all the migrations with tags in it, but forgot to rollback first, so I just did rake db:reset. Now the schema looks fine with tag and taggable in it, but if I try Tags out in the Console, by just typing Tag i get
NameError: uninitialized constant Tag
What causes this error, I thought I just did like in the Railscast.
My schema.rb looks like this:
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20121009203921) do
create_table "comments", :force => true do |t|
t.text "content"
t.integer "user_id"
t.integer "message_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "events", :force => true do |t|
t.string "name"
t.datetime "start_at"
t.datetime "end_at"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "messages", :force => true do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "taggings", :force => true do |t|
t.integer "tag_id"
t.integer "taggable_id"
t.string "taggable_type"
t.integer "tagger_id"
t.string "tagger_type"
t.string "context", :limit => 128
t.datetime "created_at"
end
add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
create_table "tags", :force => true do |t|
t.string "name"
end
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "encrypted_password"
t.string "salt"
t.boolean "admin", :default => false
t.string "phone"
t.string "street"
t.string "zip"
t.string "location"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
end
Thanks for your help!
I had to have a model, to get it work in the console.