Creating custom reusable method in rails 4 - ruby-on-rails

Guys today I'm trying to create global method for all my project models in rails 4
I created something like that under this path lib/query.rb
module Query
def custom my_query
self.where(my_query)
end
end
then added this code in this file lib/application.rb to allow rails to load the files under this path
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
then included my method in my model by using this command
include Query
now should every thing ready to use my custom method , but when I tried to call my method in the controller like that
def index
#users= Users.custom(params[:query])
end
I got the error
undefined method `custom'
what I should do now ??
why i got this error ??

I think you should use concern for your module. Add your file in app/models/concerns.
# app/models/concerns/query.rb
module Query
extend ActiveSupport::Concern
included do
#you can use a scope
scope :my_query, ->(just_a_param){ .... }
end
module ClassMethods
#or a method
def self.another_query
where(....)
end
end
end
Of course you need to include the module in your model. As concern erd default in rails, you no longer need to change config autoload paths.

As a class method, you'll need the "self."
def self.custom my_query
self.where(my_query)
end
EDIT: If you want this in all ActiveRecord models, you can add it as an initializer
#config/initializers/active_record_extensions.rb
class ActiveRecord::Base
def self.custom my_query
self.where(my_query)
end
end
If you just want this on a single class, a concern would work.

In your example, there is no reference given between your class Users and your method custom. First: if Users refers to a Ruby on Rails class it is probably called User (see also comment of japed). So change the call. Next, your User class must inherited from ActiveRecord else it would not be aware of the existence of 'where'. For details check your app/models/user.rb
Then Swards' suggestion should work for you. Stop your application and restart. Now it should work.

Guys I found the true way to make it
First my impropriety was the include that I set in the model
It should be extend Query
then it will work well
so the true code will be
create your method file under this path lib/query.rb
then set this code in it
module Query
def custom my_query
self.where(my_query)
end
end
then added this code in this file lib/application.rb
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
then extend the method in the model by using this command
extend Query
and in your controller query you can use the method like that
def index
#users= Users.custom(params[:query])
end

This is my solution, not exactly the 'Rails way', but using some sort of decorator pattern:
#user = CustomQuery.find_for(User.find(params[:search])).perform!
class CustomQuery
attr_reader :params, :klass
def initialize(klass)
#params = params
#klass = klass
end
def self.find_for(params)
CustomQuery.new(params)
find_model_for(params.tap {})
end
def perform!
return params unless params.nil?
klass.all
end
def find_model_for(klass)
#klass = klass
end
end

While I'm not sure about the process to create a global method, I can tell that your Ruby code is not valid:
def custom my_query
self.where(my_query)
end
It would need to be:
def custom (my_query)
self.where(my_query)
end

Related

Global dynamic path variable

I have several functions in my model
#app/model/game.rb
...
def uncompress_game_files_to_s3
UncompressToS3Job.perform_now(self.files, "assets/#{self.id}/game") if self.files
end
def delete_game_files_from_s3
DeleteFromS3Job.perform_now("assets/#{self.id}/game")
end
def update_game_index_file_url
files = FindFilesOnS3Job.perform_now("index.html", "assets/#{self.id}/game")
self.update_attributes(url: files.first)
end
In all these functions, I use "assets/#{self.id}/game" for S3 key attribute. I would like to use this expression as global variable aws_game_path.
I tried to initialize it in initializer file.
#config/initializers/aws.rb
aws_game_path = "assets/#{self.id}/game"
but since it is out of the model scope, it raises an error undefined method `id'. How can I declare such variable?
I think ActiveSupport::Concern Would be best practice in this case.
Follow the below step to make available to all the model
1: Create a rb lets say active_record_global_var.rb extension file in lib as lib file load first.
2: paste the following piece of code.
module ActiveRecordExtension
extend ActiveSupport::Concern
def set_global_valiable
set_global_id = self
end
module ClassMethods
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
3: Create a file in config/initializers/ directory. Let say extensions.rb and put this code snipped into this file.
4: require "active_record_global_var"
5: Now call set_global_variable instances method on model object. This method would be available for all model.
For example:
User.last.set_global_valiable.id gives you the id for user
CbResume.last.set_global_valiable.id same as for CbResume Model
hope this help you !!!
If you're only using it from that model, it doesn't need to be global. Can be just a private method:
def update_game_index_file_url
files = FindFilesOnS3Job.perform_now("index.html", game_path)
self.update_attributes(url: files.first)
end
private
def game_path
"assets/#{id}/game"
end
How can I declare such varaible.
Variables don't behave this way. It has to be a method on some object. A shared one, if you want to use this logic outside of the model too. Something like this, perhaps:
module PathHelpers
def self.game_path(game)
"assets/#{game.id}/game"
end
end
class Game
def update_game_index_file_url
files = FindFilesOnS3Job.perform_now("index.html", PathHelpers.game_path(self))
end
end

Turning extension of ActiveRecord in gem

This extension create cache_find method for all models of app (I've create this using this post).
config/active_record_extension.rb
require 'active_support/concern'
module ActiveRecordExtension
extend ActiveSupport::Concern
# add your instance methods here
def flush_find
Rails.cache.delete([self.class.name, :cached_find, id])
end
included do
after_commit :flush_find
end
module ClassMethods
def cached_find id
Rails.cache.fetch([self.name, :cached_find, id]) { self.find(id) }
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I turned this code into a gem and added to this repo.
So I want to add this methods dynamically, something like this:
class User << ActiveRecord::Base
# id, name, email, age...
cached :find, :find_by_name, :find_by_email
end
and the above code should generate cached_find, flush_find, cached_find_by_name, flush_find_by_name... You get it.
I need help to:
Test Rails.cache methods in model_caching gem.
Create code to dynamically add methods to app models based on cached method arguments.
Some links that helped me but do not meet all:
https://github.com/radar/guides/blob/master/extending-active-record.md
http://railscasts.com/episodes/245-new-gem-with-bundler
http://guides.rubyonrails.org/plugins.html
Fell free to clone and improve gem code.
You don't have to hack ActiveRecord::Base. You can add what Marc-Alexandre said right into your concern, like so:
module ActiveRecordExtension
extend ActiveSupport::Concern
...
module ClassMethods
def cached(*args)
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
Also, I would not auto include the extension directly in ActiveRecord, I think it's better to explicitly include it in the models you are going to use it.
To add code dynamically you need to hack the ActiveRecord::Base class. In another file (you usually put in lib/core_ext) you could do as follow :
ActiveRecord::Base.class_eval do
def self.cached(*args)
args.each do |arg|
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
What it does basically is takes all your arguments for cached (:find, :find_by_name, etc) and define the two methods (cache_find, cache_find_by_name) and flush_find, .. etc)
Hope this helps !

Reusing code ruby on rails

I've got a module in my project in lib/. it's content is like this :
module Search
module Score
def get_score
return 'something'
end
end
end
This Search has many different modules I need to use Score. I realize I need to add require in my model (I'm trying to use this from model). So here is my code (model) :
require 'search'
class User < ActiveRecord::Base
def get_user_score
#tried this :
p Search::Score.get_score #error
#this as well
score_instance = Score.new #error
score = Search::Score.get_score # error undefined method `get_score'
end
end
So how do I reuse the code I have in other class (module)?
To get it working you can either mix the module into your class:
require 'search'
class User < ActiveRecord::Base
include Search::Score
def get_user_score
p get_score # => "something"
end
end
Or you can define the method inside your module similar to class methods:
module Search
module Score
def self.get_score
return 'something'
end
end
end
If you do that, you can call get_score like expected:
require 'search'
class User < ActiveRecord::Base
def get_user_score
p Search::Score.get_score # => "something"
end
end
See this tutorial for a more in depth explanation about modules in Ruby.
First, see "Best Practices for reusing code between controllers in Ruby on Rails".
About reuse code as a module, take a look at "Rethinking code reuse with Modularity for Ruby".
"Modules are crippled classes"
Modules are like crippled classes in Ruby. If you look into the inheritance chain you see that a Class actually inherits from Module.
Module cannot be instanciated. So the call to .new is not working.
What you CAN do however is to specify your method as a 'class' method (I know I said it is not a class...)
So you would add a self in front like this:
module Search
module Score
def self.get_score
return 'something'
end
end
end
Then you can call this method as a class method like you tried in your code example
Search::Score is a module and not a class, so Score.new will not work.
You can try to change the signature of the get_score function to self.get_score.
In addition to def self.get_score in the above answers, there is also extend self, like so:
module Search
module Score
extend self
def get_score
return 'something'
end
end
end
and module_function:
module Search
module Score
module_function
def get_score
return 'something'
end
end
end
The latter is actually the preferred method in RuboCop (source), though in practice I personally have not seen it so often.

Ruby on Rails, calling a very big method from a model

I have a very big function in my model and I want to store it somewhere else in order to keep my model dry. I read that storing methods in ApplicationHelper and then calling them from a model is a bad idea. What is a good idea then?
I want to have a separate file with my big methods and call them from a model.
You can create a "plain old ruby object (PORO)" to do your work for you. let's say you had a method that calculates the amount overdue for a user.
So, you can create app/services/calculates_overages.rb
class CalculatesOverages
def initialize(user)
#user = user
end
def calculate
# your method goes here
end
end
Then, you can:
class User < ActiveRecord::Base
def overage_amount
CaluclatesOverage.new(self).calculate
end
end
Or, in a controller you could:
def show
#amount = CaluclatesOverage.new(current_user).calculate
end
The app/services directory could also be app/models, or the lib directory. There's no set convention for this (yet).
Use a Concern. https://gist.github.com/1014971
It's simple. In app/models/concerns create a file your_functionality.rb as follows:
module YourFunctionality
extend ActiveSupport::Concern
def your_fat_method
# insert...
end
end
And in your model simply:
include YourFunctionality

About strategy pattern and Rails

I want to incorporate the strategy pattern in my application.
I have stored under lib the following classes.
class Network
def search
raise "NO"
end
def w_read
raise "NO"
end
#...
end
AND
class FacebookClass < Network
def search
# FacebookClass specific...
end
def w_read
raise OneError.new("...")
end
end
AND
class TwitterClass < Network
def search
# TwitterClass specific...
end
def w_read
# TwitterClass specific...
end
def write
# TwitterClass specific...
end
end
Now I want to call the method search of TwitterClass from app/model/network_searcher.rb. How can I do that? Did I implemented the strategy pattern here successfully?
Going by the example in the Wikipedia, I think your app/model/network_searcher should be something like this
class NetworkSearcher
def initialize(search_class)
#search_class = search_class
end
def search_social
#search_class.search
end
def w_read_social
#search_class.w_read
end
def write_social
#search_class.write
end
end
Then in controller or where you want to invoke it, you can call like this:
search_class = TwitterClass.new # or FacebookClass.new
network_searcher = NetworkSearch.new(search_class)
network_searcher.search_social # or network_searcher.w_read_social or network_searcher.write_social
Also if you are keeping these classes in lib, for Rails 3, inorder to get these classes autoloaded, you need to add this line to config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
and also follow the naming convention for the filenames in Rails (for example TwitterClass should be named twitter_class.rb). Otherwise you will have to require these files wherever you are using these classes.
The strategy pattern is used to allow the algorithm to use to be selected at runtime. Without more details it's hard to say if this is appropriate to your problem. Assuming that it is then what you need is a way to set the search on your model and you can then use the selected algorithm elsewhere in your model. e.g.
class TheInformation
attr_writer :searcher
def other_method
..
# can use the selected searcher here
#searcher.search
..
end
end
Does that help?

Resources