I have an ActiveRecord model Reservation.
It got to the point that the class is to large and does too much.
I would like to split it into a few different ones and place those under the Reservation module.
Unfortunately this will break the app.
At this moment I see following options:
namespace it to something like ReservationConcerns or similar
add the functionality to the Reservation class itself, but physically move it to the subdir (Reservation would be in app/models/reservation.rb, Reservation::Pipeline would be in app/models/reservation/pipeline.rb etc).
So the question is how to structure the different concerns of a feature already having it as one single, bulky class without breaking the app.
If you want to split up a Ruby class into different components without changing its public interface, one solution is to use modules:
# app/models/reservation.rb
class Reservation < ActiveRecord::Base
# associations, validations, etc.
include Pipeline
end
# app/models/reservation/pipeline.rb
module Reservation::Pipeline
def some_pipeline_method
# ...
end
def other_pipeline_method
# ...
end
end
ActiveRecord also provides observers, which "is a great way to reduce the clutter that normally comes when the model class is burdened with functionality that doesn't pertain to the core responsibility of the class". Observers often make heavy use of the ActiveModel::Dirty methods.
These suggestions are complimentary: modules can help you group your interface into more local chunks, while observers can make backend details more self-contained. From here, it's difficult to be more specific without knowing exactly what pieces you have that you're trying to break out.
Related
I am a newbie in Ruby on Rails. Coming from a C# and Java background, Ruby on Rails seems bizzare, yet interesting at the same time. It's almost like coming from a class-based object-oriented world to the prototyping concept of JavaScript, or even to a functional language.
Anyways, in a traditional C# or Java MVC application, I tend to keep my models and controllers as clean as possible, extracting the business logic into service classes. My models are just POCOs/POJOs (with some calculated fields and validation at most). And my controllers just process incoming requests (relying heavily on dependency injection) and then return a view or JSON.
Yet, I do not see any clear pattern in the RoR world. Some people tend to put all their business logic into controllers, some put it into models (with ActiveRecords, it kind of makes sense, although I don't like it).
And then there is the concept of Concerns. Are they the right place to extract my business logic, instead of using services? If yes, could you include an example of proper Concers use? I still struggle with the concept of modules (are they more of namespaces, or rather interfaces)? As said at the beginning, Ruby seems like a whole new galaxy to me.
This question could get into the weeds a bit as it brings in a lot of personal preferences. However here is my take on it.
First, Concerns are not a replacement for Service classes. Concerns are a clean and nifty way to manage your mix-ins. If you are new to Ruby, mix-ins are basically a way of injecting instance and/or class methods into existing classes. For example, given these classes:
class EvilRobot < ActiveRecord::Base
def destroy(target)
...
end
end
class OrneryTeenAger < ActiveRecord::Base
def destroy(target)
...
end
end
you could dry out the code with:
require 'active_support/concern'
module EvilTools
extend ActiveSupport::Concern
included do
def destroy(target)
...
end
end
end
class EvilRobot < ActiveRecord::Base
include EvilTools
end
class OrneryTeenAger < ActiveRecord::Base
include EvilTools
end
I think that the vast majority of Rails developers, myself included, go for a fat-model, thin controller design. Just how fat though is a matter of taste. I also tend to move functionality to classes under lib if they don't fit logically within a model, or extract into an engine or gem.
I would say jpgeek reaction is part of the answer. The is a lot of movement towards service objects to clean up fat models or large controllers actions. Just create an app/services folder and create service classes like:
class TargetDestructionService
def initialize(shooter, target)
#shooter = shooter
#target = target
end
def execute
#Lot of code that causes the destruction of the target.
end
end
Then in your model or controller you would call:
TargetDestructionService.new(EvilRobot.new, Human.new).execute
Here is a nice article about it: https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
TL;DR: I don't know how organise my logic domain classes.
I have the model "Application", this model is in the "core" of the App and is the way I "enter" and operate over other models like:
#application = Application.find(params[:application_id])
#application.payment.update_attribute 'active', true
or
unless #application.report.status
or
#application.set_income(params[:income][:new_income])
so the models Payment, Income and Report are basically empty because I initialise the Application model and from there I do things "on cascade" to change the "subordinated" models. But now the Application model has more than forty methods and 600 lines.
I'm doing it right? For instance when I want to add a new Payment I like to do :
payment = Payment.create params
inside the Application model because ActiveRecord "knows" how to handle the foreign keys automatically. I could create the payment inside the Payment model using:
application = Application.find(application_id)
params[:application_id] = application.id
self.create params
but this way, I need to set the Application.id manually and that looks more verbose and not elegant.
So --if I want to reduce my Application model--, should I create modules in APP/lib directory or should I move methods to the other models?
should I create modules in APP/lib directory
Basically, yes, that's what you should do. Although I'd probably make them classes rather than modules. The pattern it sounds like you're after is called "service Objects" (or sometimes "use cases"). What this does is takes the logic from a specific operation you want to perform, and puts it in it's own self-contained class. That class then collaborates with whatever models it needs to. So, your models stay quite small, and your "Service Classes" follow the Single Responsibility Principle. Your controllers then usually call a single "service class" to do what they need to do - so your controllers stay pretty minimal too.
If you google "rails service objects" or similar, you'll find lots of great stuff, but here's some resources to get you started.
Service objects rails casts: https://www.youtube.com/watch?v=uIp6N89PH-c
https://webuild.envato.com/blog/a-case-for-use-cases/
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ (there's one section on service objects there)
Keep in mind, once you do start using service objects, you don't necessarily have to ALWAYS go through your Application model to get to the related ones. A service object might take an application_id and then do eg. #payment = Payment.find_by(application_id: application_id) and so you don't have to fetch the application instance at all and can manipulate the #payment variable directly.
The fact that Rails makes it "easy" and "pretty" to get to related models doesn't necessarily mean you should do it.
I would not worry about long controller and spec files in Rails.
These files tend to get very long and the usual advice of keeping classes and methods short does not necessarily apply for controllers and their specs.
For example, in our production system user_controller.rb is 8500 lines long and the corresponding user_controller_spec.rb is 7000 lines long.
This is the length of our top 10 controllers
1285 app/controllers/*********_controller.rb
1430 app/controllers/***********_controller.rb
1444 app/controllers/****_controller.rb
1950 app/controllers/****_controller.rb
1994 app/controllers/********_controller.rb
2530 app/controllers/***********_controller.rb
2697 app/controllers/*********_controller.rb
2998 app/controllers/*****_controller.rb
3134 app/controllers/application_controller.rb
8737 app/controllers/users_controller.rb
TL;DR: If your app has four models that are all tied to tables in your database (ie. leveraging ActiveRecord and inheriting from ActiveModel::Base), the framework is pretty opinionated toward using model classes.
Abstractions of the service class pattern can be useful in some cases, but give yourself a break. One of the advantages of Rails is that its supposed to remove a lot of the barriers to development, among many things, by making organization decisions for you. Leverage your model classes.
Let's see if this starts an epic developer bickering war.
Also, its ok to create interfaces in your models for related model creation:
class Application < ActiveModel::Base
has_one :payment
def create_payment(attrs)
payment.create(attrs)
end
end
And by ok, i mean that the framework will allow this. But remember, you're already inheriting from ActiveModel::Base which defines many instance methods, including create.
I would recommend, esp. if this is a small project and you're just getting your feet wet, to use well-named rails controllers to read and write objects to the database:
class ApplicationPaymentsController < ActionController::Base
def create
application = Application.find(params[:id])
application.create_payment(payment_params)
end
private
def payment_params
params.require(:payment).permit(:x, :y) - whatever your attr names are.
end
end
The sleekness you're looking for in abstracting foreign keys in creating a relational record is taken care of for you with Rails associations:
http://guides.rubyonrails.org/association_basics.html (good starting point)
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one (more explicit docs)
That will help you slim down models if that is your goal. Just for clarification, this is one of those things that devs are extremely opinionated on, one way or another, but the truth is that there are code smells (which should be addressed) and then there are folks who arbitrary preach file length maxes. The most important thing in all of this is readable code.
A good litmus test for refactoring working code is put it down for a few weeks, come back to it, and if its confusing then put in some time to make it better (hopefully guided by already written test coverage). Otherwise, enjoy what you do, especially if you're working solo.
In a model of mine I'm using the bing translator gem for automatic translation of a model attribute via a after_create callback:
class Place < ActiveRecord::Base
after_create :auto_translate
....
# AUTO_TRANSLATE STUFF
def initialize_bing_translator(bing_id, bing_secret)
t = BingTranslator.new(bing_id, bing_secret)
<do other stuff>
end
def auto_translate
<do stuff>
end
<further auto_translate methods>
The whole bunch of functions seems to bloat the model code a little so I want to put it into some extra module. Where exactly shall I place the .rb-file? Is this a use-case for a concern (a concept I did not fully understood)? Is it better to define a seperate module in the model file itself or to place it in /lib/user_modules/? Is there sth like a rule-of-thumb? The information available on the web are confusing me a little and I'd be glad if someone could shed some light on that issue for me!
I started putting stuff like that in subfolders of models and namespacing things accordingly. In your specific case I'd put it in something like models/place/translator.rb (or similar) and call the class Place::Translator.
If you just want a sort of logical separation but will not use the same functionality elsewhere you can use concerning instead of a separate concern.
There are many options and most of them are primarily opinion-based. It depends on if you plan to reuse that class, how complex your application already is and if the new class has any external dependency.
I would think about: create your own gem, add to app/models, add a new app/translators directory, perhaps in lib...
I am using Ruby on Rails 3.0.7 and I have tree classes what behavior is almost the same (and also the code in them model files). All those have a name and a description attribute, run same validation methods and for both there is a before_save callback that maintains data consistent providing the same functions.
I would like to refactor validation methods and callbacks in a separated class\model (I think I have to locate them related files in the \lib folder of my application).
What I have to do to make that? What code I have to add in my classes and what in the refactoring class\model?
Well, you could just make a super class from which your three models inherit. I tend to put the abstract base class in app/models alongside the models themselves.
# app/models/thing.rb
class Thing < ActiveRecord::Base
# common code goes here, such as
before_save ...
validates_length_of :foo
end
# app/models/red_thing.rb
class RedThing < Thing
# methods specific to RedThing go here
end
# app/models/blue_thing.rb
class BlueThing < Thing
# methods specific to BlueThing go here
end
If your Things have many differences such that it doesn't make sense to group them like this, you'd want to use a module instead, which is a only bit more complicated.
Rails guides has info here:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#creating-custom-validation-methods
What are the advantages and disadvantages of creating a module like:
module Section
def self.included(base)
base.class_eval do
has_many :books
end
end
def ensure_books
return false if books <= 0
end
end
...where ActiveRecord methods are used in the module instead of directly on the class(es) they belong to?
Should modules be used for methods like this?
The most obvious advantage is that you can take functionality that is shared and put it into a single place. This is just a general advantage of keeping your code organized and modularized (no pun intended) – and you should, of course, do that
Using Active Record methods does not make these Modules special in any way.
The most obvious disadvantage is that your code, as written, is a little more complex. You can't use validates_presence_of in a module directly because it does not inherit from ActiveRecord::Base. (Rails 3 is supposed to make it easier to selectively extend your own classes/modules with bits of ActiveRecord-Functionality
Instead, you need to call the Active-Record-Methods on your model class when your model is included:
module FooHelper
def self.included(other)
other.send(:has_many, :foos)
end
end
So the prime disadvantage is that your code gets a little harder to read.
If you are just breaking up a single class into separate parts and don't need to reuse the code somewhere else, you could use the concerned_with-pattern which works by reopening classes.
On the other hand, If you need more functionality, like configuration parameters for your extension, consider writing a plugin
This code can be shared by models (classes).