This is a question from a rails noob trying to understand concerns and scopes.
I always thought scopes were class methods in rails but then the other day I saw this code from DHH:
module Visible
extend ActiveSupport::Concern`
module ClassMethods
def visible_to(person)
where \
"(#{table_name}.bucket_id IN (?) AND
#{table_name}.bucket_type = 'Project') OR
(#{table_name}.bucket_id IN (?) AND
#{table_name}.bucket_type = 'Calendar')",
person.projects.pluck('projects.id'),
calendar_scope.pluck('calendars.id')
end
end
end
So the way the visible method is used is like so:
current_account.posts.visible_to(current_user)
This is what is confusing me. Self here is a collection of posts so we are acting on instances whereas the visible method seems to be meant to be used as a class method. Isn't david trying to call a class method as a dynamic scope? Could someone please clarify?
Class methods on classes which inherit ActiveRecord::Base can be used also as scopes (on ActiveRecord Relation objects).
Since the module Visible was meant to be mixed into a model which inherits ActiveRecord::Base, its class method visible_to can be used also as a scope.
If this did not clear the issue, you can implement a scope which gets all adult users (age > 20) in the following ways:
class User < ActiveRecord::Base
scope :adult, lambda { where("age > ?", 20) } # with a scope
class << self
def adult # with class method
where("age > ?", 20)
end
end
end
And use it exactly the same with User.adult
Related
I am writing classes for a SQL Database in Ruby, a few of the query methods are common throughout all my classes so I want to move these methods into a parent class. I can't figure out how to get the original class call to show up in the parent class. Since my classes are the names of my tables, I need them to have access to them in my parent method. I've tried using self.class but that just returns Class, not the actual class I'm using the method on.
Here is the code I want to move into the parent class:
require_relative 'questions_databse.rb'
class User
attr_accessor :id, :fname, :lname
def self.find_by_id(id)
user = QuestionDatabase.instance.execute(<<-SQL, id)
SELECT
*
FROM
users
WHERE
users.id = ?
SQL
user.map { |user| User.new(user) }.first
end
Now what I need to do is something like this:
require_relative 'questions_databse.rb'
require_relative 'modelbase.rb'
class User < ModelBase
attr_accessor :id, :fname, :lname
# no self.find_by_id(id)
Then I want the parent class to do something like this:
require 'active_support/inflector'
class ModelBase
def self.find_by_id(id)
object = QuestionDatabase.instance.execute(<<-SQL, id)
SELECT
*
FROM
#{self.chid_class.name.tableize}
WHERE
#{self.chid_class.name.tableize}.id = ?
SQL
object.map { |object| self.child_class.new(object) }.first
end
end
I have 4 other table classes that I use this method on, so I need to be able to have the code tell exactly what class called it so that the SQL query will run properly.
require 'active_support/inflector'
class ModelBase
def self.find_by_id(id)
p self.class.name
end
end
User.find_by_id(1) #=> "Class"
That is result when I use the self.class.name
I am learning how to code right now and this is the problem that the lesson is giving me. I know there may be easier ways to do this, but I probably haven't learned those yet.
In a class method like your find_by_id:
class ModelBase
def self.find_by_id(id)
p self.class.name
end
end
self is the class itself (so self.class is Class) so you want to look at self.name:
class ModelBase
def self.find_by_id(id)
p name
end
end
I'm using acts-as-taggable-on 2.0.6 for tagging in a Rails 3.0.9 app. I've successfully added db-backed attributes to the Tag model, for example each tag has an RSS feed so I can call #post.tags.first.feed_url to grab the feedburner URL from my database.
But while I can add attributes to the Tag model, it seems I can't add instance methods. I created the instance method:
class Tag < ActiveRecord::Base
...
def subscribable?
!feed_url.blank?
end
...
end
But when I call #post.tags.first.subscribable? I get the error:
NoMethodError: undefined method `subscribable?' for #<ActsAsTaggableOn::Tag:0x00000100d32290>
Is there a way to tell ActsAsTaggableOn::Tag objects to inherit model methods from the Tag model?
You need to add this /config/initializers/tag_monkey_patch.rb
class ActsAsTaggableOn::Tag
def subscribable?
!feed_url.blank?
end
end
I'm wondering the exact same thing. As far as i've tried, it isn't working for me..
class Tag < ActsAsTaggableOn::Tag
def total_count
...
end
end
When I call it in a dirty chained form example, like so:
my_model.my_instance.tag_counts_on(:tags).first.total_count
I get a
undefined method `total_count' for #<ActsAsTaggableOn::Tag id: 1, name: "first">
From what I understand, you shouldn't be declaring a new "Tag" model like this, but should should be overriding the class like this:
class ActsAsTaggableOn::Tag
def total_count
...
end
end
I migrated from rails 2.x to 3.x. Now when calling a controller method throws
undefined method `my_helper_method' for nil:NilClass
MyController.rb
class MyController < ApplicationController
def foo
#template.my_helper_method
end
end
MyControllerHelper.rb
class MyControllerHelper
def my_helper_method
puts "Hello"
end
end
ApplicationController
class ApplicationController < ActionController::Base
helper :all
end
How to get this working?
This is actually answered in another SO post: Rails 3: #template variable inside controllers is nil
Essentially, you can replace #template with view_context
#template is an object, in your case nil. If this object doesn't has the method (my_helper_method) in it, you cannot call it (especially not if it is nil).
Methods defined in helpers are called like regular methods. But not in controllers, they are called in views. Your helper :all just makes all helpers available to the views.
So, in your view: my_helper_method :arg1, :arg2
IF you need a method for your object (#template), you need to give your object this method.
Example:
class Template < ActiveRecord::Base
def my_helper_method
# do something on a template instance
end
end
class MyController < ApplicationController
def foo
#template = Template.first
#template.my_helper_method # which actually isn't a helper
end
end
What helpers do:
module MyHelper
def helper_method_for_template(what)
end
end
# in your view
helper_method_for_template(#template)
Mixing in a helper (be aware of having a mess in your code when mixing view helpers with views and models)
class Template < ActiveRecord::Base
include MyHelper
# Now, there is #template.helper_method_for_template(what) in here.
# This can get messy when you are making your helpers available to your
# views AND use them here. So why not just write the code in here where it belongs
# and leave helpers to the views?
end
I want to put logic in the model rather than controller.
class UsersController < ApplicationController
def somemethod
d = User.methodinmodel
end
end
class User < ActiveRecord::Base
def methodinmodel
"retuns foo"
end
end
I get an error that there is no methodinmodel for the User model.
Why?
If you want to be able to call methodinmodel on the User class in general rather than a specific user, you need to make it a class method using self:
class User < ActiveRecord::Base
def self.methodinmodel
"returns foo"
end
end
Your current method definition would only work if you called it on a user:
#user = User.create!
#user.methodinmodel # Works.
User.methodinmodel # Doesn't work.
Using the new implementation using self would allow you to call it like:
User.methodinmodel # Works.
I don't know even if its possible? I need to use instance method with/within scope. Something like this:
scope :public, lambda{ where ({:public => true}) }
and call instance method(complete?) on each record to see if it is completed. public scope here should return all records that are public and are completed and completion of a record is determined by a instance method 'complete?'
Any possibility?
Thanks
Scopes are about generating query logic using ARel. If you can't represent the logic of the complete? method in SQL then you're kind of stuck
Scopes - in rails 3 at least - are meant for chaining together query logic without returning a result set. If you need a result set to do your testing for complete you'll need to do something like
class MyModel < ActiveRecord::Base
scope :public, lambda{ where ({:public => true}) }
def self.completed_public_records
MyModel.public.all.select { |r| r.completed? }
end
end
# elsewhere
MyModel.completed_public_records
Or if you need more flexibility
class MyModel < ActiveRecord::Base
scope :public, lambda{ where ({:public => true}) }
# some other scopes etc
def self.completed_filter(finder_obj)
unless finder_obj.is_a?(ActiveRecord::Relation)
raise ArgumentError, "An ActiveRecord::Relation object is required"
end
finder_obj.all.select { |r| r.completed? }
end
end
# elsewhere
MyModel.completed_filter(MyModel.public.another_scope.some_other_scope)
I created a rubygem for this exact problem a few months back when I had the same problem.
It allows you to add methods that work on the result set of the query, but abstracts the methods into another class so its not muddled in with your model.
Check it out: https://github.com/coryodaniel/collectively