Basic models, understanding the fundamentals of classes (Ruby on Rails) - ruby-on-rails

I understand that it is best practice to refactor as much of my code as possible into the models, however I am relatively new to rails, and programming as a whole. One of the concepts that seems to be causing me a little trouble is the nature of models, and understanding the scope or the availability of methods and variables.
First of all with a typical method written in the model what are the limitations (scope) that your method can be called on? How does the .self aspect work? What controllers / views have access to methods defined in the model?
I understand that these are rather basic principles but I believe that my “assumptions” with regard to this are causing me all manner of problems.

In Model-View-Controller (MVC):
A model holds your data, and any functionality closely related to your data (low level logic)
A controller holds your business logic (high level application logic)
A view holds your presentation layer (user interface)
Views have access to any public model methods. (Note: all ruby methods are public by default.) Of course, the model object must be instantiated first in the appropriate controller method, and must be instance variables (i.e. #person) and not local variables (i.e. person) in the controller.
Controllers also have access to any public model methods.
Protected methods limit access to within the class or within any of its children. Private methods limit access to within the class, only.
It appears to me that class methods, i.e. def Person.some_method ..., are visible anywhere whether or not they are defined as public, protected, or private, although this is counter-intuitive.
Regarding your question about self... You can use self for all calls to the model's own methods from inside that model, and you won't go wrong.
e.g. for Person model having first_name and last_name columns:
class Person < ActiveRecord::Base
def full_name
"#{self.first_name} #{self.last_name}"
end
def parse_name full
self.first_name, self.last_name = full.split
end
end
However, that's overkill. You actually don't need to use self for retrieving attributes in ActiveRecord, only for setting attributes, so the following is fine:
class Person < ActiveRecord::Base
def full_name
"#{first_name} #{last_name}"
end
def parse_name full
self.first_name, self.last_name = full.split
end
end

Based on your questions, it would appear you need to read up on Object-Oriented Programming and the MVC pattern. These are not exclusive to Rails, of course.

In short, there is no real limitation in what you can use to access the Model.
Technically you can call methods from your models in your views and your controllers, or other models.
Here's how I look at it:
Models = Your application's logic
Views = Front-end for your models, tying in the pieces that you want the user (or service) to see
Controllers = Glue for Views and Models, calls model data and hands it to the view
You should shy away from calling your models directly from your view.
If you are performing more than 1 or 2 operations on an object in your controller you should probably move it into a model method.
From here, I'd reccomend you picking up a book to learn about what classes do, instance methods, class methods, etc.
I recommend "Learn to Program" from Pragmatic Programmers.
http://pragprog.com/book/ltp2/learn-to-program
From there learn about what MVC is (lots of information out there), which will help with how you understand models, views, controllers, and how they relate.

Related

Does a controller extend from a model in Rails?

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

What's the convention in decoupling components in Rails

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)

Use models for specific actions on Rails?

I'm pretty new on RoR, and I having a hard time to figure out when to use a controller or something else, like a model for specific situations that implies business logic.
Figure that I have a Post model that have a method to share it on Twitter, like this one:
class Post < ActiveRecord::Base
def send_to_twitter!
Twitter.update("#{self.title} #{self.url}")
end
end
And then, somewhere I have this:
Post.find(9).send_to_twitter!
I'm not supposed to do this, since I have business logic on a model and theoretically it's not allowed (from what I know). But it's the simplest way to do this kind of tasks.
My question is, is this the right way to do it? Is there any better way?
Thanks!
Business logic belongs in the model. Models store data, and perform actions on that data. So this is perfectly correct.
Controllers are the glue between your models and your rendered output, as well as processing user input. Controllers tell models what to do, but the models should be the ones to do it.
The old adage goes "skinny controllers, fat models". Telling your models what to do should be simple and easy (controller code), even if the code to perform those actions is complex and long (model code).
Think of your app like an Airport. The air traffic controller is your "Controller". They tell the plane (the Model) to land when it checks with the other planes (other Models) that its clear and safe. Then it tells the plane to land, but the plane lands itself.
Models should know how to perform tasks with their own data. But deciding when to do those tasks is usually what the controller does.
class AirPortController
def request_landing(plane)
if #landing_planes.size == 0
plane.land!
#landing_planes << plane
else
plane.request_again_in(5.minutes)
end
end
end
Controller's tell the models what to do, but the models do it.

Learning Basic rails: Where to put conditional business logic?

So Im trying to teach myself rails and having some trouble with figuring out where logic goes.
In my exercise, I have a payment model.
class pyament
Integer Product_Type
String Product_name
There are rules for handling payments
if the product_Type is physical, do this, if virtual do that
if the product_name is book, do this
if the product_name is cow, do that
What I cant figure out is where to put these rules.
Do I make a method in the model called process that runs these rules? Does this go logic go in the controller? Im just not clear on this.
Any insight would be appreciated.
Thanks
You should definitely keep this logic in the model, and in fact, if the logic is significantly different between different types you should use multiple models with Single Table Inheritance.
See:
http://joemcglynn.wordpress.com/2009/12/11/rails-sti-part-1/
http://joemcglynn.wordpress.com/2009/12/12/rails-single-table-inheritance-part-2/
Basically the idea is this: You're already defining Product Type -- the 'type' column is the main feature of an STI table.
With STI instead of having one model with tons and tons of conditional logic or multiple models, you have several VERY SIMILAR models with VERY SIMILAR data but somewhat different logic, so all those related models can share the same table, and inherit from a common class.
For instance:
class Product < ActiveRecord::Base
...
common logic goes here
...
end
class PhysicalProduct < Product
...
physical-product-specific logic goes here
...
end
class VirtualProduct < Product
...
virtual-product-specific logic goes here
...
end
So, in this way you can create a method like product.deliver which is defined by default in the product model to trigger shipping a product -- but in the VirtualProduct model is overridden to trigger emailing a download link instead.
ActiveRecord handles all of this very nicely (see the linked articles above for a walkthrough), and the majority of your forms and links and controllers etc. will continue to work the same way they currently do.
In general you always want to keep as much logic as possible in the models instead of the controllers, because models are easier to test, and easier to debug. In your situation STI is a nice way to keep this branching logic in the models an out of the controllers and views.
For starters, this list is a nice jumping off point.
I agree that the data manipulation / business rules best fit in the model. Keep the views and controllers clean so your model can contain all the storage structures and logical rules.
Generally you want your business logic in the models. "Fat Model, Skinny Controller" is the cool Rails phrase used to describe this. See this post for more info, and don't pay attention to the out-dated Rails syntax.

rails - root model or application model

I was just looking around rails and noticed there is an app controller but no app model.
Is there no root model in rails ? if not where do you put a piece of code that needs to be in every model.
Thanks, Alex
Nothing says your controllers have to subclass ApplicationController but it generally is the standard because the vast majority of rails applications make use of the layout capabilities (which can vary on each controller), so instead of forcing the rare ones without layouts from turning the layout off (layout nil or layout false) for each controller, they make an 'abstract' one that you can turn controller features on and off easily for your whole application.
Now for models, you could create an ApplicationModel for all of your models to subclass, but there are two things to think about:
ActiveRecord normally detects when you subclass an already subclassed ActiveRecord::Base and uses this to turn STI (single table inheritance) on.
ApplicationModel will be an actual model that is expected to have a table in your database. This can lead to problems down the line.
To fix these two problems, you have to set abstract_class to true for ActiveRecord to properly function.
class ApplicationModel < ActiveRecord::Base
self.abstract_class = true
end
In contrast to an abstract ActionController, abstract_class must set to true which means the developer must know they cannot remove this line from ApplicationModel. With ApplicationController you can do pretty much whatever you want to it.
Most models in rails inherit from ActiveRecord::Base which has all of the conventional getters and setters and the association methods, validations and support.
You could extend ActiveRecord::Base in a file in the lib directory (or anywhere in the rails load path really) and any models that are active record models would have the method(s) available. Modules are also a good way of sharing code between many models.
I think I remember some years ago, there actually existed a ann AppModel by default. Is this true or do I mix things up (I haven't been working with Rails for some years until now)?

Resources