Calling private method outputs undefined method error - ruby-on-rails

I receive undefined method 'search_type' for the code below. Can you tell me what am I doing wrong here? Probably something with calling private functions, but I can't find what the problem is.
class Entry < ActiveRecord::Base
attr_accessible :content, :rank, :title, :url, :user_id
def self.search(params)
t, o = search_type(params[:type]),search_order(params[:order])
scope = self
scope = scope.where(t) if t
scope.order(o).page(params[:page]).per_page(20)
end
private
def search_order(order)
return 'comments_count DESC' if order == '1'
return 'points DESC' if order == '2'
'rank DESC'
end
def search_type(type)
return nil unless type.present?
"entry_type = #{type}"
end
end
In the controller, I have only #entries = Entry.search(params).

It's not to do with the privateness of your methods, but the fact that search is a class method, so when you call search_order from within it, it is looking for a class method called search_order but you've defined search_order as in instance method.
Make your 2 helper methods class methods and you should be ok. if you want them to be private class methods, then
class << self
def search(...)
end
private
def search_type(...)
end
def search_order(...)
end
end
If you are wondering why #entries.search(...) works it's because I assume that #entries is something like Entry.where(...) ie, a scope and you can call class methods on scopes.

search is defined as a class method, so you should call Entry.search(params) instead of #entries.search(params).

Your method is an a class method, you cant use it form instances of your class

Related

undefined method on decorated instance

Here is a decorator
app/decorators/campaign_decorator.rb
class CampaignDecorator < Draper::Decorator
delegate_all Campaign::Campaign
def created_at
helpers.content_tag :span, class: 'time' do
object.created_at.strftime("%a %m/%d/%y")
end
end
def custom_method
'hello there!'
end
end
When I call CampaignDecorator.custom_method it doesn't find the method. Also CampaignDecorator.first.created_at returns unformated date.
Can anyone please tell what am I missing?
That is not how you use the Draper Decorator.
First things first:
CampaignDecorator.custom_method tries to find a class method called custom_method in the CampaignDecorator class. Which is definitely NOT what you want.
CampaignDecorator.first.created_at looks for objects of CampaignDecorator class and operates there ( where there are no records so first returns nil)
What you need to do is actually decorate your model. Check the documentation for that.
You first need to add the functionality to your model:
class CampaignDecorator
decorates :campaign
end
In short you could do
#campaign = Campaign.first.decorate
#campaigns = CampaignDecorator.decorate_collection(Campaign.all)
#campaigns = Campaign.scoped.decorate

Loss of context in multiple blocks

I'm trying to create a super class that will be extended. The super class will call a method that has to be implemented by the child class. The thing is, that method is called sometimes 3 blocks deep. In those blocks, I also refer to attributes of the class.
But, I get an error saying that there is no variable or method, and it's because the methods and variables are assumed to be from the block class.
This is how it looks like:
class SuperClass
attr_accessor :model
def initialize(model)
#model = model
end
def resources
s = Tire.search(get_index) do
query do
boolean do
must { term :model_id, model.id } #attr_accessor fails
must { all }
search_scope(self) #search_scope fails
end
end
sort do
sort_scope(self) #sort_scope fails
end
end
s.results
end
end
class SubClass < SuperClass
attr_accessor :params
def initialize(model, params)
#params = params
super(model)
end
def search_scope(boolean_query)
boolean_query.must { term field: params[:feild] }
#...
end
def sort_scope(sort_query)
sort_query.by :field, params[:sort_dir]
#...
end
end
search = SubClass.new(model, {})
results = search.resources # undefined method error as explained below
What I'm trying to achieve is calling the method search_scope and sort_scope (Implemented in child classes) that will set also set a few search and sort parameters. But I get undefined method 'search_scope' for #<Tire::Search::BooleanQuery:0x00000004fc9820>. As you can see, it's trying to call search_scope on the class of the block context. Same with the attr_accessor :model.
I know I can remedy this by doing
def resources
instance = self
# ...
end
And then calling instance.model and instance.search_scope, but this means my child classes have to define the instance in their own search_scope and sort_scope methods too.
I was wondering whether there is a better way to solving this?

rails callback before 'new' of a model?

I have a model base_table, and I have a extended_table which has extra properties to further extend my base_table. (I would have different extended_tables, to add different properties to my base_table, but that's non-related to the question I'm asking here).
The model definition for my base_table is like:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def self.included(base)
base.belongs_to :base_table, autosave:true, dependent: :destroy
# do something more when this module is included
end
end
end
And the model definition for my extended_table is like:
class TennisQuestionaire < ActiveRecord::Base
include BaseTable::BaseTableInclude
end
Now I what I want is the code below:
params = {base_table: {name:"Songyy",age:19},tennis_ball_num:3}
t = TennisQuestionaire.new(params)
When I created my t, I want the base_table to be instantiated as well.
One fix I can come up with, is to parse the params to create the base_table object, before TennisQuestionaire.new was called upon the params. It's something like having a "before_new" filter here. But I cannot find such kind of filter when I was reading the documentation.
Additionally, I think another way is to override the 'new' method. But this is not so clean.
NOTE: There's one method called accepts_nested_attributes_for, seems to do what I want, but it doesn't work upon a belongs_to relation.
Any suggestions would be appreciated. Thanks :)
After some trails&error, the solution is something like this:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def initialize(*args,&block)
handle_res = handle_param_args(args) { |params| params[:base_table] = BaseTable.new(params[:base_table]) }
super(*args,&block)
end
private
def handle_param_args(args)
return unless block_given?
if args.length > 0
params = args[0]
if (params.is_a? Hash) and params[:base_table].is_a? Hash
yield params
end
end
end
end
end

Ruby: Can I use instance methods inside a class method?

I have a class that contains this class method:
def self.get_event_record(row, participant)
event = Event.where(
:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => self.format_date(row[:event_start_date])
).first
event = Event.new(
:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => self.format_date(row[:event_start_date])
) if event.blank?
event
end
And I also have, in the same class, an instance method:
def format_date(date)
parsed_date = date.split('/')
# if month or day are single digit, make them double digit with a leading zero
if parsed_date[0].split("").size == 1
parsed_date[0].insert(0, '0')
end
if parsed_date[1].split("").size == 1
parsed_date[1].insert(0, '0')
end
parsed_date[2].insert(0, '20')
formatted_date = parsed_date.rotate(-1).join("-")
formatted_date
end
I'm getting an 'undefined method' error for #format_date. (I tried it without the self in front, at first). Can you not use instance methods in class methods of the same class?
Short answer is no, you cannot use instance methods of a class inside a class method unless you have something like:
class A
def instance_method
# do stuff
end
def self.class_method
a = A.new
a.instance_method
end
end
But as far as I can see, format_date does not have to be an instance method. So
write format_date like
def self.format_date(date)
# do stuff
end
Just create class method
def self.format_date (..)
...
end
And if u need instance method, delegate it to class method
def format_date *args
self.class.format_date *args
end
And i don't think that it is good idea to call instance methods from class scope
You could do YourClassName.new.format_date(your_date), although I think it's pretty clear you should be restructuring your code - this method probably doesn't belong on an instance. Why don't you extend the Date Class, or make format_date a class method on the class you are using?
EDIT: Here are a few other things to think about with your code:
Your whole format_date method goes to a lot of lengths to manipulate dates as strings. Why not use Ruby's Date Class? Using Date.parse or Date.strptime or even "01/01/2001".to_date might be useful depending on your locale
Consider extending the String class for your method, if you really need to make your own method:
class String
def to_friendly_formatted_date
Date.strptime(self, "%d/%m/%y")
end
end
"01/08/09".to_friendly_formated_date
Your class method is crying our for the find_or_initialize_by helper methods:
self.get_event_record(row, participant)
find_or_initialize_by_participant_id_and_event_type_code_and_event_start_date(:participant_id => participant.id, :event_type_code => row[:event_type_code], :event_start_date => row[:event_start_date].to_friendly_formatted_date)
end
By god it's long, but it achieves what you're trying to do more elegantly (although I'm open to argument!)

rails, how to pass self in function

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?.

Resources