Set ActiveRecord Scopes In A Loop - ruby-on-rails-3

Why doesn't this work?
class Foo
...
Status.each do |status|
scope status, where(status: status)
end
...
end
Now Foo.new it returns not an instance of Foo but an ActiveRecord::Relation.

Try this in ruby 1.9
Status.each do |status|
scope status, -> { where(status: status) }
end
or in previous ruby versions
Status.each do |status|
scope status, lambda { where(status: status) }
end
-- EDIT --
I guess your problem is somewhere else, since this code is working for me:
class Agency < ActiveRecord::Base
attr_accessible :logo, :name
validate :name, presence: true, uniqueness: true
NAMES = %w(john matt david)
NAMES.each do |name|
scope name, -> { where(name: name) }
end
end
I can create new models just fine and use the scopes
irb(main):003:0> Agency.new
=> #<Agency id: nil, name: nil, logo: nil, created_at: nil, updated_at: nil>
irb(main):004:0> Agency.matt
Agency Load (0.5ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'matt'
=> []
irb(main):005:0> Agency.john
Agency Load (0.3ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'john'
=> []
irb(main):006:0> Agency.david
Agency Load (0.3ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'david'
=> []

Related

select vs distinct vs uniq?

I am confused by Ruby's ActiveRecord uniq method. I am using it to try to get back an array of complete objects, not just a particular field.
In my Padrino app script, which saves newspaper names and scores as Score objects, the uniq method by attribute on an ActiveRecord Relation is not working, and neither is distinct, with or without SQL syntax. Can anyone explain what is going on?
class Score < ActiveRecord::Base
def self.from_today
self.where('created_at > ?', Date.today)
end
end
scores = Score.from_today
scores.class
=> Score::ActiveRecord_Relation
scores.first
=> #<Score id: 123, score: -2.55, source: "Mail", created_at: "2016-08-11 04:29:24", updated_at: "2016-08-11 04:29:24">
scores.map(&:source)
=> ["Mail", "Guardian", "Telegraph", "Independent", "Express", "Mail"]
scores.uniq(:source).count
=> 6
scores.distinct(:source).count
=> 6
scores.select('distinct (SOURCE)').count
=> 5 #AHA, it works!
scores.select(:source).distinct
=> #<ActiveRecord::Relation [#<Score id: nil, source: "Telegraph">, #<Score id: nil, source: "Mail">, #<Score id: nil, source: "Independent">, #<Score id: nil, source: "Express">, #<Score id: nil, source: "Guardian">]>
#oops, no it doesn't
In Rails 5 distinct has no parameter. In Rails 4.2 the parameter is only true or false. When true, return distinct records, when false return none distinct records. uniq is in this case only an alias for distinct
So for Rails 4
scores.select(:source).distinct.count is what you want. This restricts distinct to column source

Scope on average value

I'm trying to define a scope on my Movie model in order to select all movies with an average rating higher then the provided value.
So far I have the following models:
class Movie < ActiveRecord::Base
# Callbacks & Plugins
# Associations
has_and_belongs_to_many :categories
has_many :ratings
# Validations
validates :name, presence: true, uniqueness: true
validates :description, presence: true
# Scopes
scope :category, -> (category) { joins(:categories).where("categories.id = ?", category) }
scope :searchable, -> (query) { where("name LIKE '%?%'", query) }
scope :rating, -> (rating) { joins(:ratings).average("ratings.value")) }
end
class Rating < ActiveRecord::Base
# Callback & plugins
# Associations
belongs_to :user
belongs_to :movie, counter_cache: true
# Validations
validates :value, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
validates :user, presence: true, uniqueness: { scope: :movie_id }
end
Now I'm playing around with the query options in Rails.
What I want to do is have a scope that selects all ratings for the particular movie. Calculates the average using the value property of the rating. And if that value is equal or higher to the provided value, that movie is selected.
As in the code i've been playing with the joins and average query options, but I'm not sure how to combine them in getting what I want.
Think I found it...
scope :rating, -> (rating) { joins(:ratings).group("movies.id").having("AVG(ratings.value) > ? OR AVG(ratings.value) = ?", rating, rating) }
Generates the following query for me:
Movie Load (1.9ms) SELECT "movies".* FROM "movies" INNER JOIN "ratings" ON "ratings"."movie_id" = "movies"."id" GROUP BY movies.id HAVING AVG(ratings.value) > 1 OR AVG(ratings.value) = 1
Which is what I want I think. Will test it with some Rspec now to see if it works.

Find top scored from parent and all their children in Rails

I have a model called User, which has self-join association as this:
has_many :children, class_name: "User",
foreign_key: "parent_id"
belongs_to :parent, class_name: "User"
And it also has an association with a Post model:
User has_many post
Each Post object has a score attribute, and I am trying to find the posts for a given user and their children which have a highest score, which score is bigger than 0, and which satisfy a particular attribute. So right now, I have this method in my Post model:
def self.top_related_scored_by_user_id(user_id, max)
where(:user_id => user_id).
where(:related_image => true).
where('score > 0').
order(score: :desc).
first(max)
end
But, I would like to be able to look not just for the User with user_id, but also for their children. How can I do that?
Thanks
Its very simple:
def self.top_related_scored_by_user_id(user_ids, max = nil)
user_ids = user_ids.kind_of?(Array) ? user_ids : [user_ids]
scope = where(:user_id => user_ids)
scope = scope.where(:related_image => true)
scope = scope.where('score > 0')
scope = scope.order(score: :desc)
scope = scope.limit(max) if max.present?
scope
end
You can give an array of ids to the where clause, it will generate a condition like this:
WHERE id IN (1, 2, 3, 4)
A little improvement of your method, to make it more flexible:
def self.top_related_scored_by_user_id(user_ids, options = {})
options = { limit: 10, order: 'score DESC',
score: 0, related_image: true }.merge(options)
user_ids = user_ids.kind_of?(Array) ? user_ids : [user_ids]
scope = where(:user_id => user_ids)
scope = scope.where(:related_image => options[:related_image])
scope = scope.where('score > ?', options[:score])
scope = scope.order(options[:order])
scope = scope.limit(options[:limit])
scope
end
This way you can easily set options with the same function, and it has default values that can be overridden if needed.

How to override the default has_many condition for a relation in rails?

I would like to enter my own condition for a has_many relationship in my ActiveRecord model.
I want my condition to override the default condition.
Class User < ActiveRecord::Base
has_many :notifs, :conditions =>
proc { "(notifs.user_id = #{self.id} OR notifs.user_id = 0)" }
And it generates:
Notif Load (0.2ms) SELECT notifs.* FROM notifs WHERE
notifs.user_id = 1 AND ((notifs.user_id = 1 OR notifs.user_id =
0))
I don't want the default condition of active record (the first WHERE notifs.user_id = 1 outside parens). I want only my own. How do I specify that ?
For rails 4.1+ you can use unscope inside the association scope.
class Post
belongs_to :user
end
class User
has_many :posts, -> { unscope(:where).where(title: "hello") }
end
User.first.posts
# => SELECT "posts".* FROM "posts" WHERE "posts"."title" = $1 [["title", "hello"]]
Replace :conditions with :finder_sqllike so:
has_many :notifs,
:finder_sql => proc { "(notifs.user_id = #{self.id} OR notifs.user_id = 0)" }
More details in the documentation: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many

Retrieving an ActiveRecord length validation returns nil

I want to retrieve the maxmimum lenght validation of a ActiveRecord field in one of my views.
The following works fine in rails console and returns the correct value :
irb(main):046:0> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):047:0> micropost._validators[:content][1].options[:maximum].to_s
=> "140"
However, when I use the same code in my controller it returns nil :
class PagesController < ApplicationController
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
#content_max = #micropost._validators[:content][1].options[:maximum].to_s
end
end
...
end
I also tried to include a method in my ApplicationHelper, which also returns nil ;-(
def content_max
Micropost._validators[:content][1].options[:maximum].to_s
end
What am I doing wrong?
The _validators array might not be in the same order whether you're in the console or in a web request.
Micropost._validators[:content].find {|v| v.class == ActiveModel::Validations::LengthValidator} .options[:maximum].to_s
should do what you want.
IMHO, a better solution would be to store the length in a constant (I somehow doubt the _validators array is part of the official API) :
class Micropost < ActiveRecord::Base
MAX_CONTENT_LENGTH = 140
validates :content, :length => {:maximum => MAX_CONTENT_LENGTH}
# Rest of the code...
end
and get the length with :
Micropost::MAX_CONTENT_LENGTH