I am developing a rails app and I want all my objects to have a certain method that process them. Now, while I realize that I could write that method in each object's model, I would rather stick with the DRY (don't repeat yourself) theory and place the method in one place.
Is there a place I could place a method where all my objects have access to?
Ruby and rails offer a number of options depending on what object you want to have access to a method.
ChiuBaka's answer is one option, however rails in particular offers a number of more readable options.
If you are looking for something on the controller/view level. You can simply place it in the app/helpers/application_helper.rb file. If you want to limit access you can create controller specific helper files in the same directory.
If you are looking at models. You can simply create a base model that inherits from activerecord::base, implement your method there, and then have your models inherit from that.
class MyBase < ActiveModel::Base
def myinstancemethod
end
def myclassmethod
end
end
then
class MyModel < MyBase
end
then you can call like so
instance = MyModel.new
instance.myinstancemethod
or
MyModel.myclassmethod
Place the code you want in all of your models in a module in your /lib folder and require it in your models.
Related
Been working on Rails for a bit I can't get my head wrapped around how there is almost nothing written in a model.rb file when just creating a basic CRUD application.
I was looking at a controller.rb file and was wondering why the controller file has access to model methods like all and create when there seems to be no connection between the two files.
Should't the model object methods like modelname.all and modelname.create etc. be written in the model file instead of the controller file?
TL;DR
No, it doesn't.
General Answer
A controller does not have access to model methods as you think, in the controller you never just write all or create, you write something like User.all or #user.create. You are calling the methods on the model class or instance. You are simply using the model in the controller, but this is not limited to the controller, you could do exactly the same thing in the views if you really wanted to, or you could create custom service objects, or policy objects, or repository objects, and you could still call User.all etc from inside them too.
For a very basic application you are correct you can get by writing very little or no logic, but this is only because Rails provides us with methods and does it all for us (hooray!).
Nothing in a model file just means nothing specific to this particular model... inheriting from ApplicationRecord or ActiveRecord::Base means you have built in all the class methods (all, where, find, find_by, etc) and all the instance methods (new, create, update_attributes, etc) pre-defined for the model.
The controller determines what needs to happen, the model has the methods to make it happen, so
def index
#model = Model.all
end
Means that at the point of displaying a list of all model records, you access the model's class method all
Using rails generate model, I created two models/tables, policeman and policewoman. They are lists of officers with many attributes (age, time in the field, case solved, etc.). If I wanted to do a computation that determines who should get the next promotion to Sargent what is the conventional way to do it in rails?
I would want to create a new class just to deal with this situation, and hide all the complex computation (compare attributes between both lists) away from the caller. So maybe in a controller, say Captain, under the method show I would do
class Captain < ApplicationController
def show
promotion = Promotion.new
#ideal_sargent = promotion.sargent(Policeman.find(:all),Policewoman.find(:all))
end
end
Where do I create this Promotion class? Do I use rails generate controller to make it? Or maybe make a gem for it? Or even put it all in a model (I hear thin controllers fat models)?
EDIT:
Maybe this? If so, how do I create a model without a migration file being automatically made?
Your general idea of decoupling it from models and from controllers is a good one. Although you confuse it a bit with controllers, generators and gems...
What you want to do is:
introduce a service object which is plain ruby object
put the logic for calculating promotion order inside it
have it completely decoupled from controller and loosely coupled to police officers models
The interface to use it would be basically as you have already described:
# prepare some officers to choose from
officers = [PoliceWoman.find(1)] + PoliceMan.all
# returns the first ranking officer for promotion
to_be_promoted = SargentPromotionService.new.find_top_candidate(*officers)
Where to put this service model? I suppose it contains application specific logic, that isn't really useful outside of application. Therefore we put it in the app folder. But where in the app?
A good practice is to setup the app/domain folder. There you can put all the app specific domain models (service, policy, value objects, etc...). All you need to setup this folder is add it to the autoload paths inside the config/application.rb:
config.autoload_paths += %W(#{config.root}/app/domain)
This way you have a clear separation of rails models (take care of persistence) and domain models - where you should put most of the application specific code. If your app is really simple and small you could also skip the app/domain folder and just use the app/models. And if you follow the approach to use plain ruby objects with loose coupling, you will get easily testable, maintainable, flexible and reusable code. =)
For this purpose, I wouldn't create two tables to modeling the data. I would use a single table named polices, and keep a column as gender and another one as rank to differ policeman and policewoman, and different rank. Then I would put promote function as a class method inside the Police modal.
class Police < ActiveRecord::Base
def self.promote(param1, param2)
....
end
end
In this way you can incapsulate the business logic inside the promote function, the caller can invoke it without knowing any complex computation in it. Police.promote(a,b)
For example, I would like to have a method name 'owner' which returns the owner of the article or comment on two models, Article, and Comment.
Rather than adding
def owner
user
end
to every model, is there a way to manage multiple models with 1 method?
There are several ways to handle this:
include a module containing the method in each model
add the instance method to the ActiveRecord Base
add a method_missing which is more or less the same principle as above but a bit more hacky
The pros of 1 is that it includes the method only in the model you want but yo have to include a line...
The pros of 2 & 3 is that you'll have no need to include the module but every model will have it, even if not relevant.
I have the following variable definition:
#monday = (Time.now).at_beginning_of_week
I use that in different models, controllers, and views.
Where can I define it (and how should I define it -- ##? ) so I can define it once and use throughout my Rails application?
Should it be in environment.rb? Should it be a ##?
I would add it to application controller:
before_filter :load_date
def load_date
#monday = (Time.now).at_beginning_of_week
end
So it will be accessible in all of yours controllers and views. If you want to use it in your models, then probably you need it as some param, on example for scopes. Then your controller is place where you should pass this variable to model:
#models = MyModel.before_date(#monday)
I don't think you need to have only one instance of this variable for whole application. Initializing it is quite simple. Also it is not good when you initialize it and don't use it. For me it is hard to imagine that you need it in all of your controllers and actions.
The other way is you can define class:
class MyDate
def self.get_monday
Time.now.at_beginning_of_week
end
end
And put it in config/initializers (probably there is better place where to put it). Then you can access it from anywhere in your application:
MyDate::get_monday
Defining this value using environment.rb is not what you want, because the value would be calculated when the server starts and will remain fixed after that. If you want the value to be refreshed every week, this is a problem.
I would go with the class variable (##) in ApplicationController. But this is not accessible in the model.
What you can do is create a new module, define this value in this module, and mixin this module in any controller or model that needs it. So you would have MyModule::start_of_week with the value. Just ensure that this value gets set on every request.
In my Rails application I'm trying to make the controllers skinnier and am having difficulty with one object that I keep having to pass around.
The object represents a user's facebook session and is instantiated off of the session so it exists in the controller side of things. Many model methods use it, so it is repeatedly passed in as an argument all over the place.
This smells like something, how to DRY it up the Rails way? Thanks!
First, I would recommend using a system similar to Authlogic for your authentication. This gives you two bonuses:
You have proven, stable, tested authentication for your application
The sessions are based like Models, so you can do this kind of stuff...
class Widget < ActiveRecord::Base
def do_facebook_stuff
UserSession.find #This gets you the current session
UserSession.find.record # This gets your the user for the current session
end
end
Now you no longer need to pass the session information in, as you can do basic model-style lookups to find it. In addition to this, Authlogic has a plugin architecture that supports Facebook Connect, which may help you further.
I can give you the CakePHP way (which was originally designed to be like rails).
All CakePHP models extend the same parent AppModel, and all controllers extend an AppController.
I would make an empty parameter in the AppModel that represents your object. Then in the AppController I would store the object in the current model's parameter, if the object exists. There is a callback in the CakePHP AppController called beforeFilter() which fires before any code in the controller. The ideal place to check for the object and store it in the model would be in whatever equivalent Rails has of this beforeFilter callback.
That is unless all models don't use the object. If that is true, you could put the parameter in only the Models that use it (instead of the parent), and then in the beforeFilter of the AppModel you can check first if the Model has that empty parameter.
I know it's not Ruby, but it would look like this:
public function beforeFilter() {
if (isset($this->{$this->modelName}->yourObjectParameter)) {
$this->{$this->modelName}->yourObjectParameter = $this->yourObject;
}
}
$this->modelName is a string that corresponds to the name of the current model. the { } around $this->modelName in PHP is called complex syntax. It basically converts the string into the model object. Not sure how to do the same thing in Ruby.
You can take your method to application controller, something like this
class ApplicationController < ActionController::Base
before_filter :get_facebook_session
def get_facebook_session
#facebook_session = <your code >
end
end
And you can access #facebook_session variable from your controllers and views
cheers
sameera