rails, how to pass self in function - ruby-on-rails

message and user. my message belongs_to user and user has_many messages.
in one of my views, i call something like
current_user.home_messages?
and in my user model, i have...
def home_messages?
Message.any_messages_for
end
and lastly in my message model, i have
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
ive been trying to get the current_users id in my message model. i could pass in current_user as a parameter from my view on top but since im doing
current_user.home_messages?
i thought it would be better if i used self. but how do i go about referring to it correctly?
thank you.

You could use a lambda. In your Message model:
scope :any_messages_for, lambda {|user| where('user_id = ?', user.id)}
This would work like so:
Message.any_messages_for(current_user)
And you could add a method to your user model to return true if any messages are found. In this case you use an instance method and pass in the instance as self:
def home_messages?
return true if Message.any_messages_for(self)
end
But really, I'd just do something like this in the User model without having to write any of the above. This uses a Rails method that is created when declaring :has_many and :belongs_to associations:
def home_messages?
return true if self.messages.any?
end

You can do either of the following
def self.any_messages_for(id) #This is a class method
Message.where("to_id = ?", id).exists?
end
to call above method you have to do
User.any_messages_for(current_user.id) #I am assuming any_messages_for is in `User` Model
OR
def any_messages_for #This is a instance method
Message.where("to_id = ?", self.id).exists?
end
to call above method you have to do
current_user.any_messages_for

This stuff in your Message class doesn't make a lot of sense:
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
The scope macro defines a class method on its own and there should be another argument to it as well; also, scopes are meant to define, more or less, a canned set of query parameters so your any_messages_for method isn't very scopeish; I think you should get rid of scope :any_messages_for.
In your any_messages_for class method, self will be the class itself so self.id won't be a user ID and so it won't be useful as a placeholder value in your where.
You should have something more like this in Message:
def self.any_messages_for(user)
where('to_id = ?', user.id).exists?
# or exists?(:to_id => user.id)
end
And then in User:
def home_messages?
Message.any_messages_for(self)
end
Once all that's sorted out, you can say current_user.home_messages?.

Related

Ruby: Get result of previous method in current method

The following illustrates my problem:
User.all.testing
def self.testing
v = self
v.group_by { |user| user.username = 'username' }
end
leads to:
undefined method `group_by' for Class:0x8ca0558
However:
User.all.group_by { |user| user.username = 'username' }
works.
As mentioned in the comments, I can't explain where this group_by method that accepts a parameter is coming from, but to make your testing method work the same as User.all.group_by(username: 'username') write it as
def self.testing
all.group_by(username: 'username')
end
The use of all will create an ActiveRecord relation for you. Note that even though it says "all", if you do have any other scopes in effect these will be carried across. e.g. it would behave correctly for User.where(some: value).testing. If you call testing directly without any other scopes established i.e. User.testing then then default scope will be used.
self is a kind of ActiveRecord::Base object, and thus doesn't have a group_by method. group_by is an Enumerable method. Perhaps you mean group, which is implemented in ActiveRecord::QueryMethods?
self.testing means this is a class method and can be used like User.testing NOT User.all.testing because User.all returns active record relation.
Solution: Create scope in your model
class User < ApplicationRecord
scope :testing, -> (username) { group_by(username: username) }
end
Now you can use User.all.testing

Manipulating searched object(child) in (parent)model

In my model subject.rb i have the following defined
has_many :tutors, through: :profiles
def self.search(param)
where("name like ?", "%#{param}%")
end
So something like Subject.search("English") works perfectly fine in rails console.
What i would like to know is that if i do subject = Subject.first and i can do stuff like subject.id and it returns the subject ID to me.
Whereas when i do subject = Subject.search("English") i am unable to do something like subject.id
Because i'm trying to link the search function to my tutor.rb model with the following code.
def self.subject_search(s)
#tutor = Tutor.all
#tutor.each do |x|
y = x.subjects.search(s)
unless y.empty?
return x
end
end
end
Which works but only returns one Tutor and not all Tutors that have the subject.
I also tried this instead
def self.subject_search(s)
#subject = Subject.search(s)
if #subject
#subject.tutors
end
end
But thats when i realised #subject.tutors doesn't work, as explained above, if i do subject = Subject.search("English") i can't manipulate subject with any methods.
What am i doing wrongly?
Using a #where returns an array of objects meeting the criteria.
Subject.search('Math') => ['Math1', 'Math2', 'Math3'] # Objects of course
In your case, you should be doing Subject.find_by_name('English') which returns the first object satisfying your query. Then you can call #tutors on your Subject model assuming you have the method defined.
If you do have to use the like operator no matter what (which I do not recommend), here's what will happen.
s = Subject.search('En') # => ['English', 'Environmental Science', ..]
s.tutors # => Undefined method tutors for Array class
Here, s is an array of Subject models rather than a singular Subject which is the reason why its not working. You either need something to narrow it down more or loop through it which is probably not what you want anyway.

How do I create a "named_scope"-like class method that does nothing?

With named scopes, it was possible to create a scope that took a parameter, but if the parameter was .blank? it would do nothing. How do I achieve the same results with a class method?
Given a model like this
class Product
belongs_to :category
def self.in_category(id)
where('"category".id = ?', id) unless id.blank?
end
end
Product.all.in_category("") returns nil, but I want it to do the same thing as Product.all.
My only thought is to set up .in_category as an if..else..end with the else doing some unnecessary like where('"category".id = *') but I don't want to clutter the generated SQL with needless statements.
You can provide default values to parameters like so:
def self.in_category(id = nil) # Allows id to be blank by default
if id.blank?
all
else
where('"category".id = ?', id)
end
end
Also, you might consider passing an actual category object to this method, it's rarely wise to work with id's directly:
def self.in_category(_category = nil) # Allows id to be blank by default
if _category.blank?
all
else
where(category: _category)
end
end

rails find_by_looking_in_every_field ...or... "why is my function missing?"

This is a two parter. I'd be happy with either of the approaches below or other suggestions.
I'd like to be able to retrieve records/objects using my model by passing it a search term and having it look for that search term in any field in the model, or any field that the model deems viable. So, as an example:
class Product < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Product.where("serial_number like :find_me", { :find_me => search_term })
end
end
This is from a Product model. The same function in the Company model might look like:
class Company < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Company.where("customer_number like :find_me or name like :find_me", { :find_me => search_term })
end
end
I would love a "railsy" way to do this, such as "find_by_looking_everywhere" but I haven't been able to find such a thing. I've found lots of suggestions for searching a single field for multiple values, but not searching multiple fields for a single value. So that's "Part 1," is there a "railsy" way to do this?
"Part 2" ... using the code above, why am I getting the following exception?
undefined method `search_all_fields` for #<Class:0xa38f2ac>
I'm calling the methods using #products = Product.search_all_fields("xy3445") or #companies = Company.search_all_fields("high")?? The trace shows that the exception is being raised by just a generic class. It doesn't say #<Product...> or #<Company...>
I'm a little lost... any and all help appreciated.
Thanks, gang.
Your method is an instance method (the Model need to be instanciated to access this method). You need a Class method (means you don't need an instance of Company to call it, like the methods where(), find() etc).
class Company < ActiveRecord::Base
def say_hello
return "Hello world!"
end
end
This method say_hello can only be called from an instance of Company (instance method):
company = Company.first
company.say_hello #=> "Hello world!"
# but this will raise a NoMethodError:
Company.say_hello #=> NoMethodError
In order to define a method as a class method, you can do the following:
class Company < ActiveRecord::Base
def self.say_hello
return "Hello world!"
end
# OR you can use the name of the model instead of the self keyword:
def Company.say_hello
return "HEllo World!"
end
end
Now you can do:
Company.say_hello
#=> "HEllo World!"
# but this will fail:
Company.first.say_hello
#=> NoMethodError

Object does not get loaded

This is the weirdest thing ever happened to me with ruby/rails.
I have a model, Store, which has_many Balances. And I have a method that gives me the default balance based on the store's currency.
Store model.
class Store < ActiveRecord::Base
has_many :balances, as: :balanceable, dependent: :destroy
def default_balance
#puts self.inspect <- weird part.
balances.where(currency: self.currency)[0]
end
...
end
Balance model.
class Balance < ActiveRecord::Base
belongs_to :balanceable, :polymorphic => true
...
end
Ok, so then in the Balance controller I have the show action, that will give me a specific balance or the default one.
Balance controller.
class Api::Stores::BalancesController < Api::Stores::BaseController
before_filter :load_store
# Returns a specific alert
# +URL+:: GET /api/stores/:store_id/balances/:id
def show
#puts #store.inspect <- weird part.
#balance = (params[:id] == "default") ? #store.default_balance : Balance.find(params[:id])
respond_with #balance, :api_template => :default
end
...
private
# Provides a shortcut to access the current store
def load_store
#store = Store.find(params[:store_id])
authorize! :manage, #store
end
end
Now here is where the weird part comes...
If I make a call to the show action; for example:
GET /api/stores/148/balances/default
It returns null (because the currency was set as null, and there is no Balance with null currency), and the SQL query generated is:
SELECT `balances`.* FROM `balances` WHERE `balances`.`balanceable_id` = 148 AND `balances`.`balanceable_type` = 'Store' AND `balances`.`currency` IS NULL
So I DON'T know why... it is setting the currency as NULL. BUT if in any where in that process I put
puts #store.inspect
or inside the default_balance method:
puts self.inspect
it magically works!!!.
So I don't know why is that happening?... It seems like the store object is not getting loaded until I "inspect" it or something like that.
Thanks
Sam and Adrien are on the right path.
ActiveRecord overrides method_missing to add a whole bunch of dynamic methods including the accessors for the column-backed attributes like Store#currency. While I'm glossing over a lot, suffice it to say that when the logic is invoked then the dynamic class/instance methods are added to the Store class/instances so that subsequent calls no longer require the method_missing hook.
When YOU overrode method_missing without calling super, you effectively disabled this functionality. Fortunately, this functionality can be invoked by other means, one of which you tripped upon when you called store#inspect.
By adding the call to super, you simply assured that ActiveRecord's dynamic methods are always added to the class when they're needed.
OK finally after a lot of debugging, I found the reason...
In the Store model I have a method_missing method and I had it like this:
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
end
end
So when I was calling self.currency it went first to the method_missing and then returned null. What I was missing here was the super call.
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
else
super
end
end
But I continue wondering why after I had called puts #store.inspect or puts self.inspect it worked well?. I mean, why in that case that super call wasn't needed?

Resources