I have a workflow app written in Rails and there are a number of prompts which the user will go through. When these prompts/manual actions are "proceeded" a time stamp is stored against the a workflow model.
Code looks something like so
class Workflow < ActiveRecord::Base
end
class OpenBook < ActiveRecord::Base
belongs_to :workflow, :polymorphic => true
end
class ReadFirstPage <
belongs_to :workflow, :polymorphic => true
end
At the moment each workflow item ( OpenBook ) and spits out a bit of text (set in the model) and a forward / backward button on the view. The controllers for these workitems also do the similar things (new, done, edit, proceed, rewind).
Ideally I would like to just use the same controller and views and just change the model. Is theer something I can do with the routes eg?
resources :open_books, :controller => "workflow_item"
Im not sure how I would go about getting the correct assignments in the controller.
Or am I just doing this completely wrong and I should be using helpers?
You might have an easier time using a series of controllers for each specific object type that inherit from a common parent. That way most of the duplication can be in the base class where only the differences are expressed in the children.
To ensure your templates are rendered correctly, you may have to specify the full path to them or it will look for customized versions:
def index
# ...
render(:template => 'parent/index')
end
Usually this is redundant, but in your case you may need it or it will default to showing the non-existent child-template first.
Related
The Stage
Lets talk about the most common type of association we encounter.
I have a User which :has_many Post(s)
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
Problem Statement
I want to do some (very light and quick) processing on all the posts of a user. I am looking for the best way to structure my code to achieve it. Below are a couple of ways and why they work or don't work.
Method 1
Do it in the User class itself.
class User < ActiveRecord::Base
has_many :posts
def process_posts
posts.each do |post|
# code of whatever 'process' does to posts of this user
end
end
end
Post class remains the same:
class Post < ActiveRecord::Base
belongs_to :user
end
The method is called as:
User.find(1).process_posts
Why doesn't this look the best way to do it
The logic of doing something with the posts of the user should really belong to the Post class. In a real world scenario, a user might also have :has_many relations with a lot of other classes e.g. orders, comments, children etc.
If we start adding similar process_orders, process_comments, process_children (yikes) methods to the User class, it'll result in one giant file with lots of code much of which could (and should) be distributed to where it belongs i.e. the target associations.
Method 2
Proxy Associations and Scopes
Both of these constructs require addition of methods/code to the User class which again makes it bloated. I'd rather have all implementation shifted to the target classes.
Method 3
Class Method on target Class
Create class methods in the target class and call those methods on the User object.
class User < ActiveRecord::Base
has_many :comments
# all target specific code in target classes
end
class Post < ActiveRecord::Base
belongs_to :user
# Class method
def self.process
Post.all.each do |post| # see Note 2 below
# code of whatever 'process' does to posts of this user
end
end
end
The method is called as:
User.find(1).posts.process # See Note 1 below
Now, this looks and feels better than Method 1 and 2 because:
User model remains clutter free.
The process function is called process instead of process_posts. Now we can have a process for other classes as well and invoke them as: User.find(1).orders.process etc. instead of User.find(1).process_orders (Method 1).
Note 1:
Yes you can call a class method like this on a association. Read why here. TL;DR is that User.find(1).posts returns a CollectionProxy object which has access to class methods of the target (Post) class. It also conveniently passes a scope_attributes which stores the user_id of the user which called posts.process. This comes handy. See Note 2 below.
Note 2:
For people not sure whats going on when we do a Post.all.each in the class method, it returns all the posts of the user this method was called on as against all the posts in the database.
So when called as User.find(99).posts.process, Post.all executes:
SELECT "notes".* FROM "posts" WHERE "posts"."user_id" = $1 [["user_id", 99]]
which are all the posts for User ID: 99.
Per #Jesuspc's comment below, Post.all.each can be succinctly written as all.each. Its more idiomatic and doesn't make it look like we are querying all posts in the database.
The Answer I am looking for
Explains what is the best way to handle such associations. How do people do it normally? and if there are any obvious design flaws in Method 3.
There's a fourth option. Move this logic out of the model entirely:
class PostProcessor
def initialize(posts)
#posts = posts
end
def process
#posts.each do |post|
# ...
end
end
end
PostProcessor.new(User.find(1).posts).process
This is sometimes called the Service Object pattern. A very nice bonus of this approach is that it makes writing tests for this logic really simple. Here's a great blog post on this and other ways to refactor "fat" models: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Personally, I think that Method 1 is the cleanest one. It will be very clean and understandable write something like this:
Class User < ActiveRecord::Base
has_many :posts
def process_posts
posts.each do |post|
post.process
end
end
end
And put all the logic of process method in Post model (with an instance variable):
Class Post < ActiveRecord::Base
belongs_to :user
def process
# Logic of your Post process
end
end
That way, the very logic of a Post process belong to Post class. Even if your User model will have many "process" functions, these will be very basic and small. That seems very clean to me, as a developer.
Method 3 has many technical implications that are pretty complex and unintuitive (yourself had to clarify your question).
NOTE: If you want better performance, maybe you should use eager loading to reduce ActiveRecord calls, but that is out of the scope of this question.
First of all excuse me for the opinionated answer.
ActiveRecord models are a controversial matter. Its essence is against the Single responsibility principle since they handle both database interaction via class methods and domain objects (which use to implement their own behaviour) via its instances. At the same time they also break the Liskov Substitution Principle because the models are not sub cases of ActiveRecord::Base and implement their own set of methods. And finally the ActiveRecord paradigm often leads to code that breaks the Law of Demeter, as in your proposal for the third method:
User.find(1).posts.process
Thus, there is a trend that in order to reduce coupling would recommend to use ActiveRecord objects only to interact with the database and therefore no behaviour should be added to them (in your case the process method). Under my point of view that is the lesser evil, even though it is still not a perfect solution.
So if I were to implement what you describe I would have a ProcessablePostsCollection object (where the name Processable can be customised to better describe what the processing is about, or even neglected completely so you would simple have a PostsCollection class) that would probably be a wrapper over a list of posts using SimpleDelegator and would have a method process.
class ProcessablePostsCollection < SimpleDelegator
def self.from_collection(collection)
new collection
end
def initialize(source)
super source
end
def process
# code of whatever 'process' does to posts
end
end
And the usage would be something like:
ProcessablePostsCollection.from_collection(User.find(1).posts).process
even though the from_collection and the call to process should happen in different clases.
Also, in case you have a big posts table it would probably be wise to process stuff in batches. For that your process method could call find_in_batches on your posts ActiveRecord::Relation.
But as always it depends on your needs. If you are simply building a prototype is perfectly fine to let your models grow fat, and if you are building an enormous application Rails itself is probably not going to be the best choice since discourages some OOP best practises with things such as ActiveRecord models.
You shouldn't be putting this in the User model - put it in Post (unless - of course - the scope of process involves the User model directly) :
#app/models/post.rb
class Post < ActiveRecord::Base
def process
return false if post.published?
# do something
end
end
Then you can use an ActiveRecord Association Extension to add the functionality to the User model:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :posts do
def process
proxy_association.target.each do |post|
post.process
end
end
end
end
This will allow you to call...
#user = User.find 1
#user.posts.process
I'm very new to web-development (I feel like all my posts lately have started that way) and becoming, with time, less new to rails. I'm at a point where I can do a sizeable amount of the things required for my job but there's one nagging problem I keep running into:
How do I decide if which action I should use for a given task? index, show, new, edit, create, update or destroy?
destroy is pretty obvious and I can loosely divide the rest into two buckets with index/show in one and new/edit/create in the other. But how do I decide which one to use or if I should build one of my own?
Some general guidelines or links to further reading would be very beneficial for me.
Here is how I think of these 7 RESTful Controller actions. Take, for example, a Person resource. The corresponding PeopleController would contain the following actions:
index: List a set of people (maybe with some optional conditions).
show: Load a single, previously created Person with the intention of viewing. The corresponding View is usually "read-only."
new: Setup or build an new instance of a Person. It hasn't been saved yet, just setup. The corresponding View is usually some type of form where the user can enter attribute values for this new Person. When this form is submitted, Rails sends it to the "create" action.
create: Save the Person that was setup using the "new" action.
edit: Retrieve a previously created Person with the intention of changing its attributes. The changes have not been made or submitted yet. The corresponding View is usually a form that Rails will submit to the "update" action.
update: Save the changes made when editing a previously created Person.
destroy: Well, as you guessed, destroy or delete a previously created Person.
Of course there is some debate as to whether these 7 actions are sufficient for all controllers, but in my experience they tend to do the job with few exceptions. Adding other actions is usually a sign of needing an additional type of resource.
For example, say you have an HR application full of Person resources you are just dying to hire. In order to accomplish this, you may be tempted to create a "hire" action (i.e., /people/456/hire). However, a more RESTful approach would instead consider this the "creation" of an Employment resource. Something like the following:
class Person < ActiveRecord::Base
has_many :employments
has_many :employers, :class_name => 'Company', :through => :employments, :source => :company
end
class Employement < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :employments
has_many :employees, :class_name => 'Person', :through => :employments, :source => :person
end
The EmploymentsController's create action would then be used.
Okay, this is getting long. Don't be afraid to setup a lot of different resources (and you probably won't use all 7 Controller actions for each of these). It pays off in the long run and helps you stick to these 7 basic RESTful actions.
You can name your actions whatever you want. Generally, by Rails convention, index is the default one, show shows one item, list shows many, new and edit start editing a new or old item, and create and update will save them, respectively. destroy will kill an item, as you guessed. But all these are just conventions: you can name your action yellowtail if that's what you want to do.
You have a set of related models created through a scaffold e.g. a house, which has many rooms, which each have many windows, which each has a selection of locks.
These resources are already full of data i.e. someone has entered all the information, such as: a room called 'kitchen' has various windows associated with it and these windows each have five different locks associated with them.
Someone comes along and says:
Can you create a form that lets someone create a new project where they can select the different rooms, windows and then specify the locks that they would like for that project? (these are already in the system, nothing new to add, just the associations to a new project)
This sounds like a nested form but I have wasted a lot of time trying to solve this - there are many levels of nesting, which make this tricky. Any suggestions?
session based solution
With such deeply nested models select box on the front end wouldn't be enough...
Assuming this, you may want to create a current_house who's id live in a session (just like current_user works).
Once you have your current_house add different items by navigating to your list of items view and clicking on the add_to link :
# house_controller.rb
def add_to
current_house.polymorphic_items << Kitchen.find(params[:id])
redirect_to :back
end
But there are many approaches to this session based solution which sort of implements a cart/order system. You may want to add a current_item to add stuff in each leaf of your tree aka room of your house.
E.G after clicking on the kitchen you just added :
before_filter :set_current_item
def add_to
current_item.windows << Window.find(id)
end
current_item beeing polymorphic : a living room, a bathroom etc.
But how you implement that precisely depends on your Domain Model....
As a rule of thumb regarding nested forms I'd follow rails guidance for routes : don't go deeper than one level or you'll end up in a mess.
Yes this is a nested form. Railscasts nested forms is a great place to start.
If everything is already in the system you probably just want select boxes so they can select what they want. Also check out the .build method. If you have multiple levels of nesting you can also manually set the association by passing in the foreign key yourself.
I think you can model this with a single level of nested attributes, given the models below (based on Windows/Locks pre-existing and a room just needing to mix and match them into a set of windows with given locks):
class House < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :house
has_many :window_configs
end
class WindowConfig < ActiveRecord::Base
belongs_to :room
belongs_to :window
belongs_to :lock
end
class Lock < ActiveRecord::Base
has_many :window_configs
end
class Window < ActiveRecord::Base
has_many :window_configs
end
... based on that model setup, you could have a single house form that you dynamically add child 'room' definitions to that each have a name and a collection of window_configs which have two select boxes for each one (choose a window definition and then a lock definition). Because you're dynamically adding multiple rooms with multiple windows, you'd need some JS to populate new form elements, but it could all live in a single nested form.
form_for :house do |form|
# Dynamically add a Room form for each room you want with js
form.fields_for :room do |room_attributes|
room_attributes.text_field :name
# Dynamically add window_config forms on Room w/ JS
room_attributes.fields_for :window_config do |window_attributes|
window_attributes.select :window_id, Window.all
window_attributes.select :lock_id, Lock.all
I have a post model. Each post has a title and many snippets.
class Post < ActiveRecord::Base
has_many :snippets
end
class Snippet < ActiveRecord::Base
belongs_to :post
attr_accessible :position, :post_id
end
I want to have 4 different types of snippet i.e.:
body (text)
image (string)
code (text)
video (string)
Q1
Should I create four new models (called text, code, video and image) which extend the snippet model like so?:
class Text < Snipppet
attr_accessible :body
end
class Image < Snippet
attr_accessible :image
end
class Video < Snippet
attr_accessible :title
end
class Code < Snippet
attr_accessible code
end
Q2
How can I refer to the content of each snippet in my view when each snippet can be one of 4 different things?
In my view I'd like to put something like this:
- for snippet in #post.snippets
= place the content of the snippet here
Q3
I don't think it sounds like a good idea to have a "type" field on the snippet model as this would possibly lead to strong coupling of the database and the code. Is there some kind of rails magic that will help me out in this situation?
I kinda like the type field, actually. You could use the magic of single table inheritance, but it's well-known to be a fragile system that, surprise, includes a type field, anyway.
And then you could use the polymorphism solution, but seems a bit silly to have four different tables to represent almost exactly the same thing.
Honestly, using a simple type field and changing the interpretation based on that will probably give you the best results and fewest headaches.
As far as what you'd do in your template goes, that you can probably play by ear. One clean solution might be a simple helper to call like snippet_content which, based on the type field, will call the helper snippet_content_text or snippet_content_image or snippet_content_code or snippet_content_video. Or you can just do the if-then branching in the template, or refer to any number of partial templates (though be careful with that, since those can get slow when used unnecessarily).
I like this approach, but I always run into some subtle complications later on.
Create the appropriate views (texts/_text.html.haml, images/_image.html.haml, etc), so you can let Rails handle it for you:
= render #post.snippets
Like it or not: the "type"-field is the way to have Rails magic help you create the proper instances.
Update: polymorphic relations work too, let every snippet have their own tables. Is it a is a relation or a behaves like type of relation? You could say that Images and Texts both behave like snippets. In that case, go for a module named Snippet and mix it in.
class Post < ActiveRecord::Base
has_many :snippets, :polymorphic => true
end
class Text < ActiveRecord::Base
include Snippet
belongs_to :post, :as => :snippet
end
module Snippet
# shared behavior here
end
Many thanks to everyone who answered, but I think I've managed to fix this by using jystewart's rails 3 version of the acts_as_polymorphs gem
Here's what I did:
So let's recall, we have Posts and we have four different types of snippet. So that's 6 different models (post.rb, snippet.rb, code.rb. text.rb, image.rb and video.rb)
Posts have many snippets.
Snippets belong to posts.
Code, text, video and image are all types of snippet
And the big problem is that we don't know what type of object a snippet is, because it can be one of 4 different things.
At first, I tried to do this with Single Table Inheritance, but the objects (code, text, video and image) are, in my opinion, too different from one another for this to work well and I didn't like the fact that it would likely result in lots of empty database cells so I went along the Polymorphic route.
I couldn't get standard polymorphic associations to work here because this is not your usual polymorphic situation. Normally, when dealing with polymorphism, we're talking about something like a comments model, which can attach to multiple other models. The polymorphic entity is always the same thing. But in this case, we're talking about snippets model which can be one of 4 different things. This is not a simple belongs_to situation. Polymorphism was not happening.
Then I stumbled upon this article by m.onkey.org - which is a few years old but he basically explained that this sort of thing needs the acts_as_polymorphs gem.
So the solution was to do the following:
I create 6 models, all extending ActiveRecord::Base
add has_many_polymorphs to the post model
create a polymorphic association called "snippetable" in the snippet model
add some new fields to the snippets table through my migration file
Here's my code:
class Post < ActiveRecord::Base
has_many_polymorphs :snippets, :from => [:codes, :texts, :videos, :images], :through => :snippets
end
class Snippet < ActiveRecord::Base
belongs_to :snippetable, :polymorphic => true
end
class Code < ActiveRecord::Base
# don't have to put anything in here
end
class Text < ActiveRecord::Base
# don't have to put anything in here
end
class Video < ActiveRecord::Base
# don't have to put anything in here
end
class Image < ActiveRecord::Base
# don't have to put anything in here
end
The only other thing we need, is to stick a few new fields in the CreateSnippets migration:
class CreateSnippets < ActiveRecord::Migration
def self.up
create_table :snippets do |t|
t.references :post
t.column :snippetable_id, :integer, :null => false
t.column :snippetable_type, :string, :null => false
t.timestamps
end
end
end
And that's it! Unbelievably I can now go to rails console and do the following:
p = Post.first
p.codes << Code.create(:code=>"This is a code snippet")
p.images << Image.create(:image=>"xyz.png")
p.images << Image.create(:image=>"123.png")
p.codes.count # returns 1
p.images.count # returns 2
p.snippets.count # returns 3 !!!
Yaldi!
Anyway, it's taken me 11 days to fix this, and it REALLY depressed me that I couldn't do it. I hope this helps someone.
Here are some decent reading materials for acts_as_polymorph:
jstewart's rails 3 gem
Pratik Nait (of 37 signals) Blog post on it
What a carry on
Let's say I have two different controller/view sets that are basically identical.
Business hours and Staff hours
The first contains a business_id, the latter a staff_id.
Otherwise they look the same. id, mon_start_time, mon_stop_time, tues_start_time, tues_stop_time, etc...
1)
Is there a way I would use the same controller for these since they are so similar? It doesn't seem to make THAT much sense, but I just keep thinking about how similar they are and how much duplicate code there is.
2)
Additionally, for each one I have a form in a partial. I'm trying to use the same one for both business hours and staff hours. The partial, in the most simplified state looks like:
-form_for(obj) do |f|
=f.error_messages
%p
=select obj.class, :mon_start_time, hours
to
=select obj.class, :mon_stop_time, hours
=link_to 'Back', obj_path
So there are 3 unique things I need to pass in. The 'obj', either business_hours or staff_hours. That's okay in the form_for, but how do I get the lowercase controller name for the first parameter of the selects? I need 'business_hour', and 'staff_hour', from 'obj'.
Then I need it to know the correct 'Back' link.
I know I can pass parameters into the partial, but I'm just curious if there's a slicker way of going about this.
Thanks!
Duplicate code has a carrying cost, a cost to maintain it. We sometimes don't know how high that cost is until we refactor the duplicate code and find outselves breathing a sigh of relief: Now I can change the business rule in just one place. Now I can stop typing things twice.
You can use two controllers but still refactor the duplicate code. One way is to put the common code in a module included by both controllers.
module CommonStuff
def stuff_that_is_the_same
end
end
controller FooController < ApplicationController
include CommonStuff
def stuff_that_is_different
# Stuff specific to Foo
stuff_that_is_the_same
# More stuff specific to Foo
end
end
controller BarController < ApplicationController
include CommonStuff
def stuff_that_is_different
# Stuff specific to Bar
stuff_that_is_the_same
# More stuff specific to Bar
end
end
As far as getting the name of controller is concerned you can get it in any controller action by calling the method
controller_name
and the controller state is available in #controller instance variable to your views, so if you want to access it in your views then you can do
#controller.controller_name
Now looking at your BusinessHours and StaffHours classes, I would say the best thing to do here to make them polymorphic. The first thing you will achieve here is to get rid of an almost identical table. So check out the rails core polymorphic docs
NOTE: But the has_many_polymorphs mentioned by #amurmann is not yet available in rails core, though you can use it as a plugin. Pratik wrote a blog post about it here
For removing duplicate code from the controller, you can either put that in a module (as #Wayne said) or create a base controller from which your Business and Staff hours controllers inherit all the common functionality. Now the solution totally depends what makes more sense in your application. Personally, I will create a base controller as it is more OO, keep my classes structured and code will not be hidden in some module. But some people may think otherwise.
Have you looked into Single Table Inheritance? I think it's the all around best solution for you here.
Essentially you define an hours table and model, that looks exactly like either staff hours or business hours and includes a string column named type. Then you create subclasses of Hours to define methods and validations specific to Business and Staff hours respectively. You also might want to redefine your association to something more generic. In the example, I've called it keeper, as on one who keeps those hours.
In a nutshell rails is handling all the polymorphic behaviour for you.
Models:
class Hour < ActiveRecord::Base
# common associations/methods etc go here
end
class BusinessHour < Hour
belongs_to :keeper, :class_name => "Business"
#business hour specific logic
end
class StaffHour < Hour
belongs_to :keeper, :class_name => "Staff"
#staff hour specific logic
end
class Business < ActiveRecord::Base
has_many :business_hours, :foreign_key => :keeper_id
...
end
class Staff < ActiveRecord::Base
has_many :staff_hours, :foreign_key => :keeper_id
...
end
Route:
map.resources :business_hours, :controller => "hours", :class => "BusinessHour"
map.resources :staff_hours, :controller => "hours", :class => "StaffHour"
Controller:
class HoursController < ApplicationController
before_filter :select_class
def select_class
#class = Kernel.const_get(params[:class])
end
def new
#hour = #class.new
...
end
def show
#hour = #class.find(params[:id])
...
end
...
end
Views will look almost exactly what you have now, but you're going to want some helpers to keep the output straight.
module HourHelper
def keeper_type
#class == "BusinessHours" ? "Business" : "Staff"
end
end
-form_for(obj) do |f|
=f.error_messages
%p =select keeper_type, :mon_start_time, hours
to
=select keeper_type, :mon_stop_time, hours
=link_to 'Back', obj_path
You might also want to consider creating a custom form builder to simplify the form creation.
It sounds to me like you might be able to combine both into one resource. Let's just call the resource "hours", for this discussiion.
The problem that "hour" might belong to either a business or staff could potentially be solved by making the relation polymorph.
You can for sure solve that relation problem with has_many_polymorphs:
class Hours < ActiveRecord::Base
has_many_polymorphs :participants, :from => [:staff, :business], :through => :hours_participants
end
This would require a new table and might not be the solution with the best performance. However, it should be a DRY solution.