Fat model, thin controller - but what if mixed models? - ruby-on-rails

I am building a Ruby on Rails 4.1 app that has the following models that should make sense when you see the model names:
Domains, Teams, Users, Meetings etc
Now, a Team belongs to a domain and a User to a team, also meetings belong to a user. A domain is simply an organisation or company that is using the software.
After the creation of an admin user that user has to initially create a Domain, then create the first team, then other users can sign up.
As you can probably anticipate, in the process of creating the initial Domain and Team (which is done in the new/create of the Team and Domain controllers) I am having to access and change references in all three. So, for example, creating the first Domain will have to associate the admin user, creating a Team will involve linking it to the user, then also the parent domain.
So, it's all getting a bit mixed up, with the controller needing to access several models.
My question is, where does this logic belong? In the spirit of thin controller you would normally farm it out to the model, but it involves more than one model. Is this where the new Rails 4 concerns become useful or should I just put it in the controller?
I am relatively new to rails, so please keep that in mind if you are kind enough to reply - thanks!

Nested resources
class Domain
has_many :teams
end
class Team
has_many :users
belongs_to :domain
end
class User
has_many :meetings
belongs_to :team
end
class Meeting
belongs_to :user
end
Then in your controllers:
class TeamsController < ApplicationController
def new
#team = Team.new
end
def create
#domain = Domain.find(params[:domain_id])
#team = #domain.teams.build(params[:team])
#team.save
respond_with #team
end
end
That's what we call a "nested" controller, in the routes.rb file:
resources :domain do
resources :team
end
The URL will look like that:
/domains/:domain_id/teams
/domains/:domain_id/teams/:team_id
Same logic apply for other models. This should give you a starting point to build your application.
The following line
#domain.teams.build(params[:team])
automatically link a domain to a team setting the reference (id) for you.
However you should not do deep nesting according to rails guide so that's where the builder design pattern can come in handy.
Builder design pattern
However, if things start to get to messy, I would suggest using a dedicated ruby class to "build" your objects and their relationship. We usually call those classes a "Builder":
class TeamBuilder
attr_reader :domain, :params
def initialize(domain, params = {})
#domain = domain
#params = params
end
def build
domain.teams.build(params)
end
end
Here it's again doing very simple task. For user and meetings for example:
class UserBuilder
attr_reader :team, :params
def initialize(team, params = {})
#team = team
#params = params
end
def build
team.users.build(params).tap do |user|
user.foo = 'foo'
user.meetings.build(...)
user.meetings << MeetingBuilder.new(user, { ... })
end
end
end
class MeetingBuilder
# ...
end
Here we use the MeetingBuilder within the UserBuilder to build a meeting.
Usage:
user = UserBuilder.new(team, { ... }).build
user.save

Ideally, a model shouldn't care about, or even know about, other classes. So between the model and the controller, this kind of logic definitely belongs in the controller.
I would probably go with a third class though, taking care of that messy stuff, like a DomainFactory for example. Which is just a plain old ruby object (poro), no active record or anything, with the sole purpose of creating domains.
A tip is to read up on loose coupling and single responsibility.

Related

In rails how to allow creation of new class and models at run time

I am facing a design problem with respect to a rails app I am developing for my company product right now. My app allows creation of two classes which are subclasses of a parent class.
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
end
class DealershipCoupon < Coupon
end
When you go to the view and you want to create a new coupon, you select either of the two and a new coupon is created depending upon the params[:coupon_type]
In the controller:
if params[:coupon_type] == 'dealershipcoupon'
#coupon = DealershipCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
elsif params[:coupon_type] == 'servicecentercoupon'
#coupon = ServiceCenterCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
end
I wanna give admin users the ability to create new coupon types at the run time as well. Say, someone wants to create Repairshopcoupons class. What changes do I need to bring to the views for example add a new form or what params I need to add to the existing form to be able to create new sub classes of Coupons at run time?
I do understand that using
repairshopcoupon = Class.new()
can work. For example anonymous function like this code in the controller can work:
Repairshopcoupon = Class.new(Coupon) do
include ActiveModel::Validations
validates_presence_of :title
def self.name
"Oil Change"
end
end
#newrepairshopcoupon = Repairshopcoupon.new
#newrepairshopcoupon.save
But I am not sure.
My first questions is: What would be the proper flow if I want users to create new classes from the view. What should controller handle and how will it save?
My second question is: There are few customers who belong to both dealerships and service centers group. Each group has authority over what coupon type they can manage. I want these users who belong to multiple groups to be able to see respective coupon inventory as well as which users downloaded those. I feel the need of changing my data model so that all coupon inventory and download lists belong to exactly one authorized group but I don't have a concrete idea of what would be the best way.
My third question is: What would be the best approach to change my view/UX for creating and managing coupons so that the users of multiple groups would be able to switch between each inventory ? What would be the professional industry standard for UX deign in this case ?
Would really appreciate your help.
Letting the users of an application generate code at runtime is just a really bad idea as the amount of potential bugs and vulnerabilities is mind boggling as your basically allowing untested code to be injected into the app at runtime.
It will also wreck havoc with any class based caching in the application.
It also won't work with cloud platforms like Heroku that use an ephemeral file system which is created from the last code commit.
First off you probably don't actually need different classes for each "type" of coupon. You need to consider if the logic for each class is substantially different.
You can probably get by just by creating a polymorphic association to the issuer (the dealship or service center).
class Coupon < Coupon
belongs_to :issuer, polymorphic: true
end
If you want to avoid polymorphism than just set it up as a standard STI setup:
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
self.table_name = 'coupons'
belongs_to :service_center
end
class DealershipCoupon < Coupon
self.table_name = 'coupons'
belongs_to :dealership
end

rails controllers/forms - where to build nested models?

In my Rails app, I have a few forms that accept nested models for new objects. So for example:
class Maker < ActiveRecord::Base
has_many :cars
accepts_nested_attributes_for :cars
end
class Car < ActiveRecord::Base
belongs_to :maker
end
Before I show that form though, I need to build some of the nested models for my Maker model, or they don't show in the form. E.g.:
maker = Maker.new
3.times do
maker.cars.build
end
But where should this code ideally go? In the model as its own form_display function, in the controller, in a decorator, etc.?
Answers to this question will be somewhat opinion-based. From a separation-of-concerns standpoint, if you're already using decorators in your project it might make more sense to put the logic there than in the controller or the model.
# maker_decorator.rb
def cars
if object.new_record? && object.cars.none?
3.times { object.cars.build }
end
object.cars
end
If you don't want to deal with decorators, putting the logic in the controller seems a reasonable approach (as long as it's not being duplicated across multiple actions).
# makers_controller.rb
def new
#maker = Maker.new
3.times { #maker.cars.build }
end
I wouldn't add model code to handle this functionality since that's a rather blatant mixing of model and view concerns, though any use of accepts_nested_attributes_for will tend to push you down that route. "Form objects" avert this problem, though that might be a heavyweight solution in your case and there are few established libraries or conventions for this pattern at the moment (see #3 in this blog post).

Get record from url

I know this is a really simple question but I guess my brain and google-fu isn't working so well today.
Let's say I have an Event, with Registrants, and they can pay for the event using one or more payments.
I'm trying to create a payment linked to a registrant (who is linked to an event).
So my payment should have both registrant_id and event_id.
My URL looks something like this: (nested routes)
http://mysite.com/events/1/registrants/1/payments/new
My controller looks something like:
def create
#event = Event.find(params[:event_id])
#registrant = Registrant.find(:first, conditions: {id: params[:registrant_id], event_id: params[:event_id]} )
#payment = Payment.new params[:payment]
end
I know there is a much better way to do it, but I'm having trouble with the wording to properly google it :)
What syntax should I be using to make the .new automatically aware of the event_id and registrant_id?
Based on the discussion in the comments, there are several ways that the question can be addressed: the direct way and the Rails way.
The direct approach to creating objects that are related is to create the object using new_object = ClassName.new as suggested in the question. Then take the id of the created object and set that on an existing object (directly with existing_object.id = new_object.id or through some other method if additional logic is required). Or set the id on a new object by defining a custom initializer, such as:
class Payment
def initializer id_of_registrant
#registrant_id = id_of_registrant
end
...
end
The advantage of this approach is that it allows you to assign registrant IDs that may come from a range of objects with different classes, without having to deal with unnecessary or perhaps incorrect (for your solution) inheritance and polymorphism.
The Rails way, if you always have a direct relationship (1 to 1) between a Registrant and a 'mandatory' Payment is to use a has_many or belongs_to association, as described in the Rails guide: http://guides.rubyonrails.org/association_basics.html
For the example classes from the question:
class Registrant < ActiveRecord::Base
has_one :payment
end
class Payment < ActiveRecord::Base
belongs_to :registrant
end
You will want to use the appropriate migration to create the database tables and foreign keys that go with this. For example:
class CreateRegistrants < ActiveRecord::Migration
def change
create_table :registrants do |t|
t.string :name
t.timestamps
end
create_table :payments do |t|
t.integer :registrant_id
t.string :account_number
t.timestamps
end
end
end
Of course, if you registrants only optionally make a payment, or make multiple payments, then you will need to look at using the has_many association.
With the has and belongs associations, you can then do nice things like:
#payment.registrant = #registrant
if you have instantiated the objects by hand, or
#payment.new(payment_amount)
#registrant = #payment.build_registrant(:registrant_number => 123,
:registrant_name => "John Doe")
if you would like the associations populated automatically.
The Rails Guide has plenty of examples, though in my experience only trying the most appropriate one for your actual use case will show if there are restrictions that could not be anticipated. The Rails approach will make future queries and object building much easier, but if you have a very loose relationship model for your objects you may find it becomes restrictive or unnatural and the equivalent associations are better coded by hand with your additional business rules.
It's not great practice to set id attributes directly, as the id might not refer to an actual database row. The normal thing to do here would be to use CanCan (https://github.com/ryanb/cancan), which seems like it would solve all your problems.
EDIT:
If you're not using authentication of any kind then I'd either put the load methods in before_filters to keep things clean:
before_filter :load_event
def load_event
#event = Event.find params[:event_id]
end
or define some funky generic loader (unnecessarily meta and complex and not recommended):
_load_resource :event
def self._load_resource resource_type
before_filter do
resource = resource_type.constantize.find params[:"#{ resource_type }_id]
instance_variable_set :"##{ resource_type }", resource
end
end

Access current_user from within Rails models?

I am building an Invoicing Application that basically follows the following pattern:
Users < Clients < Projects < Invoices
Now in order to generate autoincrementing invoice numbers for each User I put this in my Invoice model:
before_create :create_invoice_number
def create_invoice_number
val = #current_user.invoices.maximum(:number)
self.number = val + 1
end
However, it seems that the current_user variable cannot be accessed from within models in Rails?
What can I do to solve this problem?
This is due to separation of concerns in Rails and is a somewhat sticky issue to deal with. In the Rails paradigm, models should have no knowledge of any application state beyond what they're passed directly, so most Rails coders will tell you that any model needing to know about a current_user is code smell.
That said, there are three ways to do this, each "more correct" (or at least I would consider them so).
First, try creating an association to the user inside the invoice and link the invoice to the user in the controller:
class InvoicesController < ApplicationController
...
def create
#invoice = current_user.invoices.create(params[:invoice])
...
end
And in your model:
belongs_to :user
def create_invoice_number
self.user.invoices.maximum(:number) + 1
end
If that doesn't work, do this manually in the controller. It's true that controllers should always be as skinny as you can manage, but since this is clearly an application-level concern the controller is the place to put it:
class InvoicesController < ApplicationController
...
def create
#invoice = Invoice.create(params[:invoice])
#invoice.update_attribute(:number, current_user.invoices.maximum(:number))
...
end
Lastly, if you really, really want to bridge the controller and model, you can do so with ActionController::Sweepers. They are not intended for this purpose but will certainly get the job done for you.
there should not be any arise of such case still if you want then make use of observers in rails

Using one form to create two models with overlapping attributes in Rails

This post seems good for how to create two models with one form. But how would you do it if the two models share one or more of the attributes?
That post seems fairly outdated, I would recommend using accepts_nested_attributes_for and fields_for in your form instead. That said, overlapping attributes should probably be set in your model's callbacks. Say you want a project's name to be automatically set to first task's name.
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks
before_validation :set_name_from_task
private
def set_name_from_task
self.name = tasks.first.name
end
end
If your 2 models are completely unrelated, you can assign certain params to them directly in the controller.
def create
#foo = Foo.new(params[:foo])
#bar = Bar.new(params[:bar])
#bar.common_attr = params[:foo][:common_attr]
# validation/saving logic
end
Although this is not a great practice, this logic should ideally be moved into models.

Resources