In my Activity model, I have a default scope:
default_scope where(:subject_id => Log.get_subject_id)
Problem is in Log.get_subject_id, default value is 0. Here is my Log model:
##subject_id = 0
def self.set_subject_id(val)
##subject_id = val
end
def self.get_subject_id
##subject_id
end
When I change value of ##subject_id via Log.set_subject_id(10) in controller and then I try Activity.all, it always give me bad result. SQL:
SELECT "activities".* FROM "activities" WHERE "activities"."subject_id" = 0
Where is a problem? Thanks!
With that form of default_scope, your Log.get_subject_id call will be evaluated when the class is parsed so you're really saying something like this:
default_scope where(:subject_id => 0)
However, you can use a block with default_scope to delay the evaluation of the scope until you try to use it and, presumably, Log.get_subject_id will have a useful value:
default_scope { where(:subject_id => Log.get_subject_id) }
It is happening because of the scope caching, try wraping it with lambda:
default_scope lambda { { :conditions => { :subject_id => Log.get_subject_id } } }
Related
I'm trying to refactor the Companies_Controller#index method to encompass less logic by moving most of the query into a scope, company_search_params.
What is the best way to pass the param to the model scope? I'm getting an error thrown back, wrong number of arguments (given 0, expected 1). I'm relatively new to writing scopes and couldn't find much on passing arguments/conditions that was applicable in the Rails Guide.
Companies Controller
def index
params[:name_contains] ||= ''
Company.company_search_params(params[:name_contains])
#search = Company.company_search_params
#companies = #search.page(params[:page])
end
Company Model
scope :company_search_params, ->(name_contains){
where(
<<-SQL
"name LIKE :match OR subdomain LIKE :match", { match: "%#{name_contains}%" }
SQL
).where(is_archived: false).order(name: :asc)
}
Thanks for your help.
using named_scope sample and info
scope :named_scope, lambda {
|variable1, variable2|
where...
order...
}
#when you call it from your controller
Model.named_scope("value 1","value 2")
for your problem
in your company.rb
scope :company_search_params, lambda {
|name_contains|
where(
<<-SQL
"name LIKE :match OR subdomain LIKE :match", { match: "%#{name_contains}%" }
SQL
).where(is_archived: false).order(name: :asc)
}
company_controller.rb
def index
#search = Company.company_search_params(params[:name_contains])
#companies = #search.page(params[:page])
end
I'm trying to change self.rank:
def self.rank(courses)
courses.sort_by do |course|
[course.list_order ? course.list_order : Float::INFINITY,
course.upcoming? ? 0 : 1,
course.title
]
end
end
def upcoming?
start_date && start_date > Time.now.utc
end
into a scope, as self.rank(courses) returns an array rather than an activerecord scope.
current progress (will update til complete):
scope :ranked, -> { order(:list_order) }
possible resources:
Conditional order for nested model based on field
SQL Conditional Order By
scope :just_what_i_want, lambda { |course_id| where(:course_id => course_id) }
scope :ranked, lambda { |list_order| order(list_order ? list_order : Float::INFINITY).order('start_date DESC').order(:title) }
use like:
Courses.just_what_i_want([1,2,3,4,5,6]).ranked('course_id')
I think this should work?
I am using Ruby on Rails 3.2.2 and I am experimenting the Squeel gem. I would like to know if (in some way, by using the Squeel gem or not) it is possible to "add" SQL clauses related to a scope method "directly" in a where clause. That is, I have:
class Article < ActiveRecord::Base
# Note: This is a scope method.
def self.created_by(user)
where(:user_id => user.id)
end
# I would like to use a scope method like the following.
#
# Note: Code in the following method doesn't work, but it should help
# understanding what I mean.
def self.scope_method_name(user)
where{ created_by(user) | ... & ... }
end
end
So, when I run Article.scope_method_name(#current_user).to_sql then it should return something like the following:
SELECT articles.* FROM articles WHERE articles.user_id = 1 OR ... AND ...
I tryed sifters but those (at least for me) are intended to be used exclusively in other Squeel statements. That is, if I state a sifter then I cannot use that to scope ActiveRecords because that sifter returns a Squeel::Nodes::Predicate object instead of an ActiveRecord::Relation.
You have to drop down into more raw AREL for OR operations
def self.scope_method_name(user)
t = arel_table
where(
(t[:user_id].eq(user.id).or(
t[:blah].eq('otherthing')
).and([:bleh].eq('thirdthing'))
)
end
Or something along those lines.
You can chain scopes like Article.by_author(user).by_editor() but this joins all the conditions with ANDs. So, to get around this, you can write individual scopes (not chaining them) using Squeel like:
class Article < ActiveRecord::Base
scope :by_author, ->(user) { where{author_id == user.id} }
scope :by_editor, ->(user) { where{editor_id == user.id} }
scope :by_title, ->(token) { where{title =~ "%#{token}%"} }
scope :by_author_or_editor, ->(user) { where{(author_id == user.id)|(editor_id == user.id)} }
scope :by_author_or_editor_and_title, ->(user, token) { where{((author_id == user.id)|(editor_id == user.id))&(title =~ "%#{token}%")} }
end
Or you can use sifters:
class Article < ActiveRecord::Base
sifter :sift_author do |user|
author_id == user.id
end
sifter :sift_editor do |user|
editor_id == user.id
end
sift :sift_title do |token|
title =~ "%#{token}%"
end
scope :by_author, ->(user) { where{sift :sift_author, user} }
scope :by_editor, ->(user) { where{sift :sift_editor, user} }
scope :by_title, ->(token) { where{sift :sift_title, token} }
scope :by_author_or_editor, -> (user) { where{(sift :sift_author, user)|(sift :sift_editor, user)} }
scope :by_author_or_editor_and_title, ->(user, token) { where{((sift :sift_author, user)|(sift :sift_editor, user))&(sift :sift_title, token)} }
end
This gives you your scopes that return an ActiveRecord::Relation, so you can in theory further chain them.
I'm building a user ranking system, and am trying to assign user.rank values with a name.
I wanted to define something like this in my User model and then be able to reference it when displaying each user's rank, but this probably isn't the best way:
class User < ActiveRecord::Base
RANK_NAMES = {
'Peasant' => (0..75),
'Craftsman' => (76..250),
'Vassal' => (251..750),
'Noble' => (750..1500),
'Monarch' => (1501..999999)
}
Perhaps it would be better to define a method in a controller or helper like:
if user.rank == 0..75
rank_name = "Peasant"
elsif...
But not sure how to do that. Anyone have any thoughts? I'm not even sure what to call what it is I'm trying to do, thus making it difficult to research on my own.
It could be something even as simple as this, assuming user.rank exists.
class User < ActiveRecord::Base
...
def rank_name
case self.rank
when 0..75
'Peasant'
when 76..250
'Craftsman'
when 251..750
'Vassal'
when 750..1500
'Noble'
when 1501..999999
'Monarch'
end
end
...
end
If rank_name is specific to the User, I'd make it a method of User.
You could try something like below. It might give you some ideas.
class User
RANKS = [
{:name => 'Peasant', :min => 0, :max => 75},
{:name => 'Craftsman', :min => 76, :max => 250}
# ...
]
attr_accessor :rank
def rank_name
# TODO what happens if rank is out of range of all ranks or rank is nil
# or not an integer
User::RANKS[rank_index][:name]
end
private
def rank_index
User::RANKS.index { |r| (r[:min]..r[:max]).include? #rank }
end
end
user = User.new
user.rank = 76
puts user.rank_name # -> Craftsman
I very often want to use dynamic finders to specify NOT NULL. So…
this works:
Widget.find_all_by_color('blue')
this works:
Widget.find_all_by_color(nil)
But how can I do
SELECT * FROM `widgets` WHERE `color` IS NOT NULL;
?
Two ways depending on how specific you want to be:
# in your model
class Widget < ActiveRecord::Base
# first way
named_scope :coloured, {:conditions => ["color IS NOT NULL"]}
# second way
named_scope :not_null, lambda{|*args| (field=args.first ? {:conditions => ["#{field} is not null",field]} : {}) } }
end
# in your controller
#coloured_widgets = Widget.coloured.all # using first way
#coloured_widgets = Widget.not_null(:colour).all # using second way
I hope it helps.
Cheers
Widget.find(:all, :conditions => "color IS NOT NULL")
Try this:
Widget.all(:conditions => "color IS NOT NULL")
Not quite as elegant, but this should work:
Widget.find(:all, :conditions => "'color' IS NOT NULL")