I have a rails app with several models.
I have a function that I want to access from several models.
What's the best place to put this code and how can I make it accessible from the models that need to get at it?
My understanding is that helpers are just for views. Is this correct?
It seems wrong to create a plug-in and put it in the vendor folder - this is my code and integral to my app. Is this correct?
Thanks.
The simplest solution would be to create a module under lib and mix this into the models that need it, for instance, in lib/fooable.rb:
module Fooable
def do_foo
end
end
And then in your various models:
class MyModel < ActiveRecord::Base
include Fooable
end
No need to require fooable.rb, the Rails autoloading mechanism will find it for you as long as it's named using correct conventions.
In order to lessen the repetition of code, you could also create a main class which would include that module and the simply inherit from it from every model you'd like to share the behaviour.
Something like:
module Fooable
def do_foo
end
end
class ParentModel < ActiveRecord::Base
include Fooable
end
class Product < ParentModel end
class User < ParentModel end
class Item < ActiveRecord::Base end
Thus, in that example, both Product and User would share the do_foo functionality and Item would not.
Related
I have a simple problem, but I don't know what the best approach is to do it.
I have two ActiveModel classes (say SimpleUser and User). I'd like to share some method implementations between them (say a full_name method that aggregates first name and last name, to stay simple).
In a classical OO language, I'd have used an abstract class that my two classes would have extended. But in Ruby (and even Rails), what's the best approach?
You can put your code in the app/models/concerns and include them in both models.
For example:
app/models/concerns/user/shared_methods.rb
module Concerns::User::SharedMethods
extend ActiveSupport::Concern
def full_name
...
end
end
app/models/user.rb
class User < ActiveRecord::Base
include Concerns::User::SharedMethods
...
end
app/models/simple_user.rb
class SimpleUser < ActiveRecord::Base
include Concerns::User::SharedMethods
...
end
There are several options:
You could create a module, e.g UserAttributes, which would be included in each User class
Or you could create a base class as in conventional object-oriented language and use it as an abstract class. If you want to make sure that no one misuses it, you could make it's constructor private.
It would look like this:
class User
private_class_method :new
end
Note: In ActiveRecord (not ActiveModel) there is a built-in way to make class abstract.
self.abstract_class = true
I plan on giving different logic to different categories and I don't know if I should make 20 different models or if I can have just one StoreCategory model and put the logic in there. Some of the logic will be small and others large.
So If I had different store categories like discount, online, delivery, retail, etc, and wanted to give them their own special logic, what should I do?
It's a good question. In your place, I would create a class "Category" and my other classes inherit from the first. In the Category class I would put the shared code and the specific code in subclasses. You could use polymorphic associations if it's necessary. It's just an idea.
Edit 1 : Add code example
class StoreCategory < ActiveRecord::Base
before_save :something_private
def a_public_function
#...
end
protected
def a_protected_function
#...
end
private
def something_private
#something after save
end
end
class DiscountCategory < StoreCategory
def my_first_function
#I could use a_public_function and a_protected_function
end
end
class OnlineCategory < StoreCategory
def a_protected_function
#I could use a_public_function
#I could override a_protected_function
#I could use super to run the Category's function
end
end
the function something_private is called after the save of each subclasses. I think it's clear and organisated. It works like ApplicationController class.
I have a standard ActiveRecord model with the following:
class MyModel < ActiveRecord::Base
custom_method :first_field, :second_field
end
At the moment, that custom_method is picked up by a module sent to ActiveRecord::Base. The functionality basically works, but of course, it attaches itself to every model class, not just MyModel. So if I have MyModel and MyOtherModel in the same action, it'll assume MyOtherModel has custom_method :first_field, :second_field as well.
So, my question is: How do I attach a method (eg: def custom_method(*args)) to every class that inherits from ActiveRecord::Base, but not by attaching it to ActiveRecord::Base itself?
Any ideas appreciated.
===
Edit
The custom_method is currently attached to ActiveRecord::Base by the following:
module MyCustomModule
def self.included(base)
base.extend(self)
end
def custom_method(*args)
# Zippity doo dah - code goes here
end
end
ActiveRecord::Base.send(:include, MyCustomModule)
Do you know about descendants?
ActiveRecord::Base.descendants
You have to be sure to touch the models before calling it.
See excellent discussion here:
Is there a way to get a collection of all the Models in your Rails app?
I concur with the commentors above that you may want to consider adding your methods to the meta class, or an intermediary class, or a Module mixin.
I have some functions that I use in every single model and I'd like to place these in something like ActiveRecord::Base so I won't have to name the same functions in all of my models.
I'm not even sure if something like this is according to best practices. Perhaps some Rails pros could show me something better.
Write a module that contains your required methods, and include MyModule as needed.
You certainly can do as #derekerdmann suggested and create an abstract base class for your models:
class MyBaseModel < ActiveRecord::Base
abstract_class = true
def my_method(*args)
#code goes here
end
end
class MyModel < MyBaseModel
end
Just don't forget the abstract_class = true line or single table inheritance is assumed.
Personally, I prefer the mixin methodology, because if your models ever diverge in common functionality, you can group common functions into separate modules and include them as needed.
Remember that you can still use standard object-oriented practices in Rails. Make a class that extends ActiveRecord::Base with all your common functionality, and then extend that class for each of your real ActiveRecord models.
I have this method in my book model but now I realize I need this in a category model as well:
def proper_user?(logged_in_user)
return false unless logged_in_user.is_a? User
user == logged_in_user
end
I now have this method duplicated in the books model and the category model. Both category and books has belongs_to :user and both have the user_id:integer in the table as well. I simply want to extract this somewhere where so I can its DRY.
i tried to put the method in application_controller.rb but it says undefined method `proper_user?' for #
Thanks
Jeff
I think you'd want to be able to call this method like this:
book.proper_user?(current_user)
So it would work best to define it in each model rather then in User. This is best done by mixing in a module with the method:
module UserMethods
def proper_user?(logged_in_user)
# ... etc ...
end
end
and including it in each model:
class Book < AR::Base
include UserMethods
class Category < AR::Base
include UserMethods
The module can go in a source file in config/initializers, or you can put it elsewhere and change config.autoload_paths in config/environment.rb to point to the location.
Since it is related to the User model, why not put it there?