Define a model method that gets all records - ruby-on-rails

I have a rails model called "Post", it represents user's posts on a website.
Each "Post" has a method called "score" which takes its count of views and calculates a score. Score is not stored in the db, it's just a method:
class Post < ActiveRecord::Base
has_many :votes
def score
self.votes * 2
end
# This should return an object with all the posts, ordered by score.
def get_all_posts_sorted_by_rank
...
end
Error in Rails console:
2.0.0-p451 :001 > Post.get_all_posts_sorted_by_rank
NoMethodError: undefined method `get_all_posts_sorted_by_rank' for Post (call 'Post.connection' to establish a connection):Class

If you want it to be a class method, you should use:
def self.get_all_posts_sorted_by_rank
...
end
It's probably what you want.
Otherwise, if you want an instance method, you should use the way you are, but you have to instantiate your object before:
#post = Post.new #or find(params[:id]) or some other way
#post.get_all_posts_sorted_by_rank #use it
But the first case seems more probable.

Related

Overriding existing model in Rails 4

I am trying to override an existing active record model but the new active record model doesn't have methods from the old active record model. Here is the code that I am trying to use
class ModelA < ActiveRecord::Base
def method_modela_1
logger.info "I am in method_modela_1"
end
def method_modelb_2
logger.info " I am in method_modelb_2"
end
end
Next I override the ModelA with ModelB
class ModelB < ModelA
def foo
logger.info "foo method from model B"
end
end
So now I am trying to write a code that is trying to access find_or_create_by using the following code
some_variable = ModelB.find_or_create_by(:id => 1234)
but it says ***** NoMethodError Exception: undefined method 'find_or_create_by' for ModelB:Module**
What am I missing? For record I am using 'rails', '4.2'
The keyword is "NoMethodError Exception: undefined method 'find_or_create_by' for ModelB:Module".
You have a module ModelB somewhere and, due to load order and/or constant lookup rules, it shadows your model.

Using Rails helpers in model

In my model I have a method that marks a record as pending by changing its status to 2. After which it calls another method in another controller to create a notification containing details of the record that was changed.
e.g.
class Page < ActiveRecord::Base
def pend_page
self.update(status: 2)
Notification.create_notification("#{link_to self.title, pages_path(:status => 2)} marked as pending", #current_user)
end
end
However it seems Rails doesn't pass helpers for link_to and the routes to the models... as I get the error: undefined method 'pages_path' for #<Page:0x007fd15c996c88>.
How can I make it so that the link_to and pages_path work?
I'm using Rails 4.2.5.1
edit: here is what create_notification looks like:
class Notification < ActiveRecord::Base
belongs_to :user
def self.create_notification(content, user)
notification = Notification.new
notification.content = content
notification.user_id = user.id
notification.status = 0
notification.save
end
end
This should go in either a service object or in a PORO (plain old ruby object). A model's concerns should begin and end with database related functionality, anything else is in the wrong place.

RoR: Update attribute in a model from a unrelated controller

I need to pass a value to attribute in a model from a different controller with no direct relation between them. In the below example I need to update farming_year in the Field Model from the Planting controller.
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: self.get_farming_year) # doesn't get the farming_year
end
def get_farming_year
#farming_year # passing the value directly will work #farming_year=2015!!
end
def farming_year=(val)
#farming_year = val # passing the value directly won't work #farming_year=2015!!
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
#field.save
flash.now[:success] = #field.get_farming_year # it works and gives the correct year
end
when I changed the #farming_year in the get_farming_year method to #farming_year=2016, then the code will work and will give the correct Crops records. the flash message in the code above without any change will give the correct year from the model. I think my main issue is passing the farming year from get_farming_year method to getting_crops method.
Hint: the framing year is belong to the Crop not to the Field, so I don't need to add it to the Field table.
Any ideas how to achieve that?
Your code has a number of issues.
attr_accessor
Why are you using an attr_accessor? You should store the value on a model attribute, in the database. If your Field model doesn't already have a farming_year attribute, create a migration to add it to the database by running these commands:
$ rails g migration AddFarmingYearToField farming_year:integer
$ rails db:migrate
If you're running Rails <= 4, use rake db:migrate instead of the second command.
Doing this means you don't need to use attr_accessor, or define getters and setters.
PlantingController#new
This method isn't working for you because you haven't defined the correct methods, and you're not saving the instance.
In your Field model, you've defined a farming_year method, but you haven't defined a farming_year= method, which is what the setter should be. Change your farming_year method definition to farming_year=. Alternatively, use the method I described in 1., then you won't have to.
Make sure you're saving the model object once you're done with it - call Field#save, which returns truthy on success and falsy on failure; or call Field#save!, which returns truthy on success and raises an exception on failure.
The main issue with my code was using attr_accessor which I didn't need it, so, I've replaced "attr_accessor :farming_year" with a class variable "##work_year =''", and updated the getter and setter method as in the below code
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
##work_year =''
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: farming_year) #now this can request the getter method and get the year
end
def farming_year # getter method
##work_year ||= ''
end
def farming_year=(val) #setter method
##work_year = val
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
##field.save NO need for this line
flash.now[:success] = #field.farming_year
end
Thank you all for your kind support:)

Collection Method within Rails Model gets error NoMethodError: undefined method `collect'

I'm having trouble applying a method to a collection within a model. Here's what my model looks like:
class MyModel < ActiveRecord::Base
def self.range(min, max)
where(id: min..max)
end
def self.my_collection_method
collect{|x| x.id}
end
end
This works:
MyModel.range(53, 55)
# [#<MyModel id: 53>, #<MyModel id: 54>]
And this works:
MyModel.range(53, 55).collect{|x| x.id}
# [53, 54]
But when I try to be fancy and use my_collection_method instead of collect, I get this error:
MyModel.range(53, 55).my_collection_method
# NoMethodError: undefined method `collect' for #<Class:0x007fcbad3efc30>
How can I get this to work?
Thanks for your time and wisdom!!
I don't know what you're trying to achieve but you should be able to use it when you call scoped first
def self.my_collection_method
scoped.collect {|x| x.id}
end
without scoped, you are trying to call self.collect which is not a method of ActiveRecord models. having scoped there means you want to apply collect to the current chain.
You've defined my_collection_method on the model, not on a collection - they are separate classes. You can normally achieve what you want to do inside a model method:
class MyModel < ActiveRecord::Base
def self.range(min, max)
where(id: min..max)
end
def self.my_collection_method(min, max)
range(min, max).collect{|x| x.id}
end
end
The only other way is by attaching a method on the fly to the collection, but that's going to be pretty ugly.

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