Rails 3 - default_scope - ruby-on-rails-3

I would like to order the article tags on my index page by popularity as opposed to by creation date i.e. the tags with the most amount of articles in them from highest to lowest. My model is as follows?
class Tag < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => true
# order by creation
default_scope :order => 'created_at DESC'
has_many :taggings, :dependent => :destroy
has_many :articles, :through => :taggings
end

I recommend using a counter cache column to store a taggings_count (which automatically gets updated when new taggings).
And then your default scope can look like this:
default_scope :order => 'taggings_count DESC'
For more info, search for "counter_cache" in the Rails guide for AR associations

Related

Rails sort model on properties of a related model

Given the following models:
team.rb
class Team < ActiveRecord::Base
has_many :events, :dependent => :destroy
has_many :challenges, :through => :events
validates :name, :presence => true, :uniqueness => true
end
challenge.rb
class Challenge < ActiveRecord::Base
has_many :events, :dependent => :destroy
has_many :teams, :through => :events
validates :name, :presence => true, :uniqueness => true
validates :flag, :presence => true, :uniqueness => true
end
event.rb
class Event < ActiveRecord::Base
belongs_to :team
belongs_to :challenge
validates :team, :presence => true
validates :challenge, :presence => true
end
I want to display the teams with the highest "rank".
Where the highest ranking team has the most challenges completed (events).
If there is a tie at X events then the team that completed the Xth event
first has the highest rank.
So I can easily sort the teams based on the number of events and then show them.
Like so:
def index
#teams = Team.includes(:events).
select("*, COUNT(events.id)").
group("teams.id, events.id").
order("COUNT(events.id) DESC")
end
However I don't know how to handle the case where there is a tie.
Does anybody know a good way of doing this with SQL?
I would rather use SQL to do this versus performing an extra step
on the app server.
Thanks!!
You could try this:
#teams = Team.includes(:events).
select("*, COUNT(events.id) AS event_count, MAX(events.created_at) AS last_event_created_at").
group("teams.id, events.id").
order("event_count DESC, last_event_created_at ASC")
So I got it working.
Thanks xnm. You sent me in the right direction. Can't believe I forgot about MAX. :(
I also had to fix my GROUP BY...
Here is the working version:
class Team < ActiveRecord::Base
...
def self.ranked
all(:select => "teams.*, COUNT(events.id) AS challenges_completed, MAX(events.created_at) AS last_event",
:joins => "LEFT OUTER JOIN events ON events.team_id = teams.id",
:group => "teams.id, events.team_id",
:order => "challenges_completed DESC, last_event ASC")
end
end

How to sort by created_at column of association in rails?

Here are my associations:
Class Post
belongs_to :user
has_many :favorites, :dependent => :destroy
has_many :favoriters, :through => :favorites, :source => :user
end
Class User
has_many :posts
has_many :favorites, :dependent => :destroy
has_many :favorited, :through => :favorites, :source => :post
end
Class Favorites
belongs_to :user, :post
end
I want to sort users' favorite posts by the created_at column of the Favorites association. However, this sorts by the Post created_at attribute, not the Favorites created_at attribute. How can I sort by the Favorites created_at attribute?
#posts=#user.favorited.order('created_at DESC')
You need to specify which table you want to use in the order by clause.
#posts = #user.favorited.order('posts.created_at DESC')
ought to do it.
One nice trick is to use the rails console when inspecting associations. Specifically, it helps to use the 'to_sql' method on Active Record queries you are performing.
For instance:
% bundle exec rails console
> u = User.last
> u.favorited.order('created_at DESC').to_sql
use this in your post model for set default order:
default_scope { order("created_at DESC") }

How do I delete a record from all tables it is referred to?

Good morning fellow Overflowers,
Small problem with model associations. I have these model associations:
class Categorization < ActiveRecord::Base
belongs_to :exhibit
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :exhibits, :through => :categorizations
acts_as_indexed :fields => [:title]
validates :title, :presence => true, :uniqueness => true
end
class Exhibit < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations, :source => :category
acts_as_indexed :fields => [:title, :bulb]
validates :title, :presence => true, :uniqueness => true
belongs_to :foto, :class_name => 'Image'
end
So, essentially Categorization ends up with these columns (date/time stamps omitted):
categorization_id, exhibit_id and category_id.
My problem is that when I delete an Exhibit, its reference on the Categorization table is not deleted thus getting a DB error on my view. I have to first unassign the Exhibit from any Category and then delete it safely. Or (given for example that the Exhibit I delete has :exhibit_id=>'1') when I give in the rails console: Categorization.find_by_exhibit_id(1).destroy
Thanks for any help!!
You can set the :dependent options on associations that you want Rails to follow when you delete their parents:
class Exhibit < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
...
end

Rails 3 - Restricting Article Tags

I am looking to remove any duplicated tags being displayed and have a maximum number of 10 tags on display on the index page. Any suggestions on how I might do this?
/controller/tags_controller
class TagsController < ApplicationController
def show
#tag = Tag.limit(10).all
#tag = Tag.find(params[:id])
#articles = #tag.articles
end
end
end
model/tag.rb
class Tag < ActiveRecord::Base
validates :name, :uniqueness => true
#default_scope :order => 'created_at DESC'
has_many :taggings, :dependent => :destroy
has_many :articles, :through => :taggings
end
To avoir duplicate and to order by published date, in your tag model :
validates :name, :uniqueness => true
default_scope :order => 'created_at DESC'
To fetch the ten first tags, in your controller :
#tags = Tag.limit(10).all
Voila!

Setting up a polymorphic has_many :through relationship

rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer
I have created my models as shown in the preceding code. Articles will be one of many models which can have tags. The category model will contain all categories which may be assigned. The tag model will be a polymorphic join-table which represents tagged relationships.
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :categories, :through => :taggable
end
class Category < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :articles, :through => :taggable
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
I can't seem to get this to work, I can do it non polymorphic, but I must have something wrong with the polymorphic part. Any ideas?
Edit: Still not getting this right:
class Article < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
You simply cannot make the join table polymorphic, at least Rails does not support this out of the box. The solution is (taken from Obie's Rails 3 way):
If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so you must use the :source_type option. In most cases you will have to use the :source option, since the association name will not match the interface name used for the polymorphic association:
class User < ActiveRecord::Base
has_many :comments
has_many :commented_timesheets, :through => :comments, :source => :commentable,
:source_type => "Timesheet"
has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
:source_type => "BillableWeek"
It's verbose and the whole scheme loses its elegance if you go this route, but it works:
User.first.commented_timesheets
I hope I helped!