I'm working through the 'Ruby On Rails 3 Essential Training' and have received a problem when using name scopes. When finding records and using queries withing the Rails console everything went smoothly until I tried to use a name scope in my subject.rb file. This is my code in the subject.rb file.
Class Subject < ActiveRecord::Base
scope :visible, where(:visible => true)
end
I saved the .rb file and restarted my Rails console but when I run from my rails console:
subjects = Subject.visible
I get: ArgumentError: The scope body needs to be callable.
Does anyone know why I'm getting this error.
The scope's body needs to be wrapped in something callable like a Proc or Lambda:
scope :visible, -> {
where(:visible => true)
}
The reason for this is that it ensures the contents of the block is evaluated each time the scope is used.
I got the same error , while before my solution I had a space between where and ( like below
scope :registered , -> { where ( place_id: :place_id , is_registered: :true ) }
after i removed the space between where and ( like below i made my page working
scope :registered , -> { where( place_id: :place_id , is_registered: :true ) }
Yes, indeed, this is rails 4 way of calling scopes. You'd need to change it, if you're upgrading to Rails 4 from Rails 3.
What you are using: scope :visible, where(:visible => true) goes for eager loading, and has been deprecated in Rails 4.
scope :visible, where(:visible => true)
This line of code gets evaluated when the particular class is loaded, and not at the time, this very scope is called.
There are a few cases when this thing does matter, like:
scope :future, where('published_at > ?', Time.now)
scope :future, -> { where('published_at > ?', Time.now) }
In first case, ? will be replaced with the very time the class would have been loaded, but the second & correct case, that time will be used at which the scope would have been called on the class.
Related
I have a scope in Rails 5 that checks for the presence of a string value.
scope :category, ->(retro) {where retro: retro
if retro.present? }
Now when this scope is called and the param from user input passed is null, the sql query generated is
'SELECT * FROM categories WHERE categories.retro IS NULL'
How do I solve this?
Surround your where function call with parenthesis:
where(retro: retro) if retro.present?
The if was being applied to the parameter and not to the method call.
scope :category, ->(retro) { where(retro: retro) if retro.present? }
Without the brackets, it's assumed that you want the if to be part of the SQL
In Rails4, you can use in below format. I guess same applies in Rails5
scope :category, ->(retro) {
unless retro.blank?
where(retro: retro)
end
end
or
scope :category
def self.category(retro)
unless retro.blank?
where retro: retro
end
end
Reference link
Rails 4 - Do not scope if conditions
I have the following methods defined in a plugin:
class ReArtifactProperties < ActiveRecord::Base
unloadable
#attr_accessible :artifact_type
scope :without_projects, :conditions => ["artifact_type != ?", 'Project']
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
{:conditions => {:project_id => project_id}}
}
Your code has several issues, some of which are violations of syntax rules of Ruby, while others violate return value requirements of Rails.
The cause of your current error is a syntax error. The block to the lambda must be defined in the same line as the lambda method.
Now, if you have fixed this, you will notice that your code will throw other exceptions once you use the scope. The reason for that is that the return value of a scope is expected to be an ActiveRecord relation, not just a simple Hash.
Your scope definition should thus look similar to this:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
where(:project_id => project_id)
}
Now given that Rails is smart enough to figure out how to get the ID from an object for a query, you can even get rid of the project_id logic in it and reduce your scope definition to
scope :of_project, lambda { |project|
where(:project_id => project)
}
Your code has several issues, some of which are violations of syntax rules of Ruby, while others violate return value requirements of Rails.
Yes you were right about the lambda block
Initially the code was:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
{:conditions => {:project_id => project_id}}
}
After Correction, it reduced down to this:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
where(:project_id => project_id)
}
Now given that Rails is smart enough to figure out how to get the ID from an object for a query,
Knowing the MVC architecture which I learned from http://rubyonrails.org/
How do I understand the core functions of RAILS w.r.t Redmine Tool?The mysql default database that it uses has a very huge model structure currently.Could it be reduced down to understand the core functions of Rails?
I am trying to make an app in Rails 4.
I am using CanCanCan for permissions and Role_Model for roles management.
In my ability.rb, I have defined student abilities as:
elsif user.try(:profile).present? && user.profile.has_role?(:student)
student_abilities
and then:
def student_abilities
can :read, Project.visible.current.available
In my project.rb I have defined scopes as:
scope :visible, lambda { joins(:sweep => :disclosure).where('disclosures.allusers' => 'true')
.joins(:sweep => :finalise).where('finalises.draft' => 'false') }
scope :current, lambda { where('project.start_date >= ?', Date.today)}
scope :available, lambda { where('closed =', 'false')}
When I try to start the server and generate a view, I get this error:
NoMethodError at /project_invitations
undefined method `available' for #<Project::ActiveRecord_Relation:0x007fdde41f2ee8>
When I try removing available from the end of the ability, so that its just:
can :read, Project.visible.current
I get this error:
entry for table "project"
LINE 1: ..." = 'true' AND "finalises"."draft" = 'false' AND (project.st...
^
I don't know why it won't let me read the end of the error message.
Can anyone see what I've done wrong?
Check the table name. Is it really called "project", not "projects"?
The way you describe scopes is a bit weird. E.g. instead of where('closed
=', 'false') I would describe it as where(closed: false), minimizing the number of SQL-aware fragments
I want to hide past events if they are defined and get all other. How to show all documents even if :once_at is nil and if :once_at is defined then hide these ones which are expired?
My recent approach, shows only events with defined :once_at, (I tryed with :once_at => nil, but without results):
default_scope where(:once_at.gte => Date.today)
or (also not working)
default_scope excludes(:once_at.lte => Date.today)
When do you think Date.today is evaluated? If you say this:
default_scope where(:once_at.gte => Date.today)
Date.today will be evaluated when the class is being loaded. This is almost never what you want to happen, you usually want Date.today to be evaluated when the default scope is used and the usual way to make that happen is to use a proc or lambda for the scope:
default_scope -> { where(:once_at.gte => Date.today) }
The next problem is what to do about documents that don't have a :once_at or those with an explicit nil in :once_at. nil won't be greater than today so you'd best check your conditions separately with an :$or query:
default_scope -> do
where(
:$or => [
{ :once_at => nil },
{ :once_at.gte => Date.today }
]
)
end
I have a two scopes in my user model:
scope :hard_deactivated, where(:hard_deactivated => true)
scope :soft_deactivated, where(:soft_deactivated => true)
So far so good
OR
I want to create a scope :deactivated, which will include all users where hard_deactivated is true OR soft deactivated is true. Obviously I could just do this:
scope :deactivated, where("hard_deactivated = ? or soft_deactivated = ?", true, true)
but this does not feel very dry.
NOT
Also I would like to create an inverse scope :not_hard_deactivated. I could do this:
scope :not_hard_deactivated, where(:hard_deactivated => false)
but again, this feels bad, especially if my scope becomes more complex. There should be some way or warpping the SQL generated by the previous scope in a not clause.
Use an arel table:
hard_deactivated_true = arel_table[:hard_deactivated].eq(true)
soft_deactivated_true = arel_table[:soft_deactivated].eq(true)
scope :deactivated, where(hard_deactivated_true.and(soft_deactivated_true))
scope :not_hard_deactivated, where(hard_deactivated_true.not)
See: Is it possible to invert a named scope in Rails3?
For the "NOT" part, you can do something like this:
extend ScopeUtils
positive_and_negative_scopes :deactivated do |value|
where(:hard_deactivated => value)
end
And implement this method in a separate module:
module ScopeUtils
def positive_and_negative_scopes(name)
[true, false].each do |filter_value|
prefix = ("not_" if filter_value == false)
scope :"#{prefix}#{name}", yield(filter_value)
end
end
end
Regarding the "OR" case, you might be something similar, depending on what your recurring pattern is. In the simple example above it's not worth it, as doesn't help readability.
scopes_with_adjectives_and_negatives :deactivated, [:soft, :hard]
module ScopeUtils
def scopes_with_adjectives_and_negatives(name, kinds)
kinds.each do |kind|
positive_and_negative_scopes name do |filter_value|
where("#{kind}_#{name}" => filter_value)
end
end
scope :"#{name}", where(kinds.map{|kind| "#{kind}_#{name} = ?"}.join(" OR "), true, true)
scope :"not_#{name}", where(kinds.map{|kind| "#{kind}_#{name} = ?"}.join(" AND "), false, false)
end
end
You should use sql snippet in where method (like in your second example), or more 'sugar' gems like squeel