How to decide which action to use - ruby-on-rails

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.

Related

Rails API: One Request, Multiple Controller Actions

I have multiple models that in practice are created and deleted together.
Basically I have an Article model and an Authorship model. Authorships link the many to many relation between Users and Articles. When an Article is created, the corresponding Authorships are also created. Right now, this is being achieved by POSTing multiple times.
However, say only part of my request works. For instance, I'm on bad wifi and only the create article request makes it through. Then my data is in a malformed half created, half not state.
To solve this, I want to send all the data at once, then have Rails split up the data into the corresponding controllers. I've thought of a couple ways to do this. The first way is having controllers handle each request in turn, sort of chaining them together. This would require the controllers to call the next one in the chain. However, this seems sorta rigid because if I decide to compose the controllers in a different way, I'll have to actually modify the controller code itself.
The second way splits up the data first, then calls the controller actions with each bit of data. This way seems more clean to me, but it requires some logic either in the routing or in a layer independent of the controllers. I'm not really clear where this logic should go (another controller? Router? Middleware?)
Has anybody had experience with either method? Is there an even better way?
Thanks,
Nicholas
Typically you want to do stuff like this -- creating associated records on object creation -- all in the same transaction. I would definitely not consider breaking up the creation of an Authorship and Article if creating an Authorship is automatic on Article creation. You want a single request that takes in all needed parameters to create an Article and its associated Authorship, then you create both in the same transaction. One way would be to do something like this in the controller:
class Authorship
belongs_to :user
belongs_to :article
end
class Article
has_many :authorships
has_many :users, through: :authorships
end
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
#article.authorships.build(article: #article, user_id: params[:user_id])
if #article.save
then do stuff...
end
end
end
This way when you hit #article.save, the processing of both the Article and the Authorship are part of the same transaction. So if something fails anywhere, then the whole thing fails, and you don't end up with stray/disparate/inconsistent data.
If you want to assign multiple authorships on the endpoint (i.e. you take in multiple user id params) then the last bit could become something like:
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
params[:user_ids].each do |id|
#article.authorships.build(article: #article, user_id: id)
end
if #article.save
then do stuff...
end
end
end
You can also offload this kind of associated object creation into the model via a virtual attribute and a before_save or before_create callback, which would also be transactional. But the above idiom seems more typical.
I would handle this in the model with one request. If you have a has_many relationship between Article and Author, you may be able to use accept_nested_attributes_for on your Article model. Then you can pass Authorship attributes along with your Article attributes in one request.
I have not seen your code, but you can do something like this:
model/article.rb
class Article < ActiveRecord::Base
has_many :authors, through: :authorship # you may also need a class_name: param
accepts_nested_attributes_for: :authors
end
You can then pass Author attributes to the Article model and Rails will create/update the Authors as required.
Here is a good blog post on accepts_nested_attributes_for. You can read about it in the official Rails documentation.
I would recommend taking advantage of nested attributes and the association methods Rails gives you to handle of this with one web request inside one controller action.

Minimizing instance variables in views

I'm seeking brainstorming input for a Rails design issue I've run across.
I have simple Book reviews feature. There's a Book class, a User class, and a UserBook class (a.k.a., reviews and ratings).
class User < ActiveRecord::Base
has_many :user_books
end
# (book_id, user_id, review data...)
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
In the corresponding book controller for the "show" book action, I need to load the book data along with the set of book reviews. I also need to find out whether the current user (if there is one) has contributed to those reviews.
I'm currently running two queries, Book.where(...) and UserBook.where(...), and placing the results into two separate objects passed on to the view. Now, while I could run a third query to find whether the user is among those reviews (on UserBook), I'd prefer to pull that from the #reviews result set. But do I do that in the controller, or in the view?
Also worth noting is that in the view I have to draw Add vs Update review buttons accordingly, with their corresponding ajax URLs. So I'd prefer to know it before I start looping through a result set.
If I detect this in the controller though, I'll need three instance variables passed in, which I understand is considered distasteful in Rails land. Not sure how to avoid this.
Suggestions appreciated.
This smells like a case for has_many through, which is designed for cases where you want to access the data of a third table through an intermediate table (in this case, UserBook)
Great explanation of has_many :through here
Might look something like this:
class User < ActiveRecord::Base
has_many :user_books
has_many :users, through: :books
end
Then you can simply call
#user = User.find(x)
#user.user_books` # (perhaps aliased as `User.find(x).reviews`)
and
#user.books
to get a list of all books associated with the User.
This way, you can gain access to all of the information you need for a particular user with a single #user instance variable.
PS - You'll want to take a look at the concept of Eager loading, which will prevent you from making extraneous database calls while fetching all of this information.

Challenging Rails Question with Form Based on Existing Resources

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

Rails, self-joined table, proper way of creation

For my web application I need to implement a supervisor/student relationship. I need to join my "Person" table with itself through the "Supervision" table.
class Person < ActiveRecord::Base
has_many :supervised, :class_name => 'Supervision', :foreign_key => 'supervisor_id'
has_many :supervisors, :class_name => 'Supervision', :foreign_key => 'supervised_id'
end
class Supervision < ActiveRecord::Base
belongs_to :supervised, :class_name => 'Person'
belongs_to :supervisor, :class_name => 'Person'
end
Now I need help regarding the controller. I'm not sure if I need two controllers, one for supervised and one for supervisors, or just one "Supervision" controller.
Both the student and supervisor must be able to create a "Supervision". I'm just not sure how to let the controller know whether the current user needs to be the supervisor or the student. Any thoughts?
You could create two controllers, but that would not be DRY, so it is probably best avoided. You can either set up your routes so the URLs for Prof/Student appear to be different, but actually map to the same controller.
How many students a prof has:
the_prof = Person.find( *my record number* )
the_prof.supervised.count
Who they are is that same thing, so show their names
the_prof.supervised.each do |student|
puts student.name
end
How to determine who is a professor or not? I would add a boolean flag to the people table: is_prof
My initial thought was the way to determine if a person was a student is they have no supervised. If a professor, they have no supervisor, but that breaks down if a Professor gets rid of all his students or the Student gets rid of all his Professors. Suddenly, we're in the land of undefined, which is BAD.
The flag also makes it easy to segregate all the professors and student, so you can do
profs = Person.find_by_is_prof( true )
studs = Person.find_by_is_prof( false )
(make sure to index that field in your database)
I'm guessing you'll end up making at least two controllers: one for people acting as supervisors, ie: one to manage one's subordinates; and one to manage one's own person record. You may even have another which manages a person's sign up and allows them to select their supervisor, as part of a kind of a wizard-style step.
Try mocking out how you want the application to behave first by sketching out some wireframes. Those will help you figure out which resources need to be changed from where.
If you find, for instance, that you need a list of one's subordinates, then that's probably a SubordinatesController#index page. Adding a subordinate would probably be a #new / #create pair in that controller.
Controllers are really about figuring out how the UI will respond to different user actions. Setting /my/ supervisor, and noting that I'm /someone else/'s supervisor are probably to very different things at the UI level. Just because they happen to reside in the same table doesn't mean that the UI has to reflect that symmetry.
It's strange that one would be able to change their own supervisor list. I think that's where the weirdness of your question arises.
Perhaps that's actually a side-effect of changing some other membership, like moving to a different group in the organization, in which case the reassignment would be part of its own controller.

How to access and submit related polymorphic models in the same form, in Rails?

Suppose I have 3 models, Car, Motorcycle and Truck, and for each I have to enter a bunch of stuff, such as a list of known previous owners, traffic tickets, license plates, etc. So I created a model for each (PreviousOwners, PreviousPlates, etc) and set up polymorphic associations for the related models.
The problem is, how can I enter all of that using just one form, kind of like this:
Car #123
Known previous owners:
Jason Jazz
Brian Bass [add another]
Known previous license plates:
12345
67890 [add another]
Current status:
Cleared
(this is a dropdown select menu, CurrentStatus is also a polymorphic association, but with predefined values.)
etc
This is proving to be a bitch, way beyond my level of expertise (newbie here). The resources are not nested and almost everything I find on multiple models is for nested resources, and nothing seems to apply to polymorphic associations.
(This is just an example, I know ideally I should have a Vehicle model with 'Car', etc, as categories, but it's just to illustrate the real need for polymorphic models in my case.)
Thanks.
Maybe the PresenterPattern is helpfull too:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
The basic idea is to create a presenter which acts like a model and processes all the incoming data from your form and distributes it to the models. This way it's also easy to create multiple instances of lets say PreviousOwner and attach it to Car.
Check the link out!
You can use the new nested attributes in Rails 2.3, but there is a certain way you have to write it to make it work. The trick is that you need to create the actual polymorphic object, then build the class that has the belongs to polymorphic clause in it. This is an example I found at Ryans Scraps, posted by a user named: Superslau (I've cleaned it up a good bit for here):
This feature is really awesome. I have
implemented this with polymorphic
associations, and it works!
class Task < ActiveRecord::Base
has_many :assets, :dependent=>:destroy
accepts_nested_attributes_for :assets, :allow_destroy => true
belongs_to :workable, :polymorphic => true
end
class Upload < ActiveRecord::Base
has_one :task, :as => :workable, :dependent=>:destroy
accepts_nested_attributes_for :task, :allow_destroy => true
end
Upload is a kind of task. All tasks
can have one or more assets uploaded.
I took me a while to figure out that I
should use the Upload model as the
parent. So in one form, I can create
an upload, and it’s corresponding task
entry, along with a file upload.
in my controller:
def new
#upload = Upload.new
#upload.task = Task.new
#upload.task.assets.build
end
Don’t
worry if that doesn’t make any sense,
I just wanted to let people know that
accepts_nested_attributes_for works
just fine with polymorphic
associations. Thanks Eloy!
Very well, nested form builders doesn't have to be associated with nested resources AFAIK.Can you post your models code as well?
There is a RailsCast on Complex Forms that might help you with building a single form from multiple models.
If the car/motorcycle/truck models are identical, you should add a type column to your vehicle model. If they're not, you should use STI (single table inheritance).
But yeah, need to see your models first before I can give you code.
You can avoid this and make things a bit simpler by introducing a Vehicle model. The Vehicle model can have all your PreviousOwners, PreviousPlates, etc collections, and then your Truck, Car and Motorcycle models can has_one Vehicle.

Resources