How to design different 'views' of a model RESTfully? - ruby-on-rails

By 'view' here I mean different combinations of properties of the model, not the view of the traditional MVC. For example, I have the following model:
class Game < ActiveRecord::Base
has_many :players
belongs_to :status
has_one :deck
has_many :turns
has_one :current_turn, :class_name => 'Turn', :conditions => ['turn_num = ?', '#{self.turn_num}']
end
I've written a full_xml method for Game that I use for the 'normal' get operation, so that I can include certain properties of players and current_turn, and then I don't have to do GETs on every player all the time. I also don't want to include ALL the properties and children and children's properties of the Game model on every GET
Now, however, I want to GET a game history, which is all the turns (and their properties/children). Initially I thought of a new model w/out a corresponding table, and then realized that wasn't necessary because the data and relationships are already there in the game and turns models. I also thought about writing a new action, but I thought I read somewhere that in the RESTful world, you shouldn't be writing any actions other than the core 7.
BTW, I'm thinking here of returning xml, because I'm using a Flex front end instead of rails views.

You have a couple of options here - I would use "nested resources" so you end up with a /game/:game_id/turns route which calls 'index' on the Turns controller. The other option is to create a GameHistory controller, which might be useful if there is additional logic associated with your game history.
There is not a one-to-one correspondence between controllers and models; there is however a one-to-one correspondence between controllers and RESOURCES. A game history is a whole different resource from a Game, just like a user session resource is different from an actual user resource (this is commonly used to allow for RESTful logins as well as RESTful user management) Hopefully this helps :)

Yes, nested resources was the answer. This Railscast explains it nicely. I had briefly tried nested resources before, and couldn't get it to work. It was returning all the child resources, not only the nested resource of the parent resource. This was because I assumed Rails was automagically doing that for me, which it doesn't. The Railscast explains that you still have to make changes to the controller of the child resources, like so:
class TurnsController < ApplicationController
# GET /turns
# GET /turns.xml
def index
#game = Game.find(params[:game_id])
#turns = #game.turns
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #turns.to_xml( :except => [:created_at, :updated_at] ) }
end
end
... more methods
end
You also have to edit your routes.rb file. In this case, I want nested routes for both players and turns of the game, so I did this:
map.resources :games do |game|
game.resources :players
game.resources :turns
end

Related

What is the best design practise to create tables from same controller using same actions

I have a project that involved genealogy. It starts by asking you questions you (current user), then the same questions about your father, then exactly the same questions about your mother and goes on until grandparents of both sides.
Unfortunately, I have tried 3 different approaches and implementations, each time correcting past mistakes as I am pretty new to rails.
I would like to get a recommendation/guidance from this community regarding the best design approach that I should follow having design problems.
So I think that the best way is to come up with different tables to benefit later when I need to put this info in a single family tree. So, one table for the current user, one for the father, mother, etc having a model for each table BUT using a single controller because I have exactly the same html.erb form and each time changes the headers to adapt the sibling question.
I had successfully created the flow and actions such as new, create, show etc BUT my problem is the following:
The flow of my questions is consecutive, and at the very end shows a family tree. Therefore when the user clicks continue, the data are saved in the equivalent table of my database and the flow continues for the next table.
I am stuck for more than 8 hours on how to make the create method change from father to mother having this code:
def create
#user = User.new(user_params)
if #user.save
redirect_to :controller => 'users', :action => 'new_father'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name, :surname, :dob, :location)
end
where users is the name of Users_Controller and 'new_father' is a view in the same controller (new_father.html.erb). Other views exist like current_user, new_mother etc.
So, the first redirection is achieved successfully as data are stored in the database (first redirection = going from current user to his father), but then I can't manage to go from father to mother in the same controller and the form stays stacked in the new_father.html.erb view having the following code:
def create
#user = User.new(user_params)
if #user.save
redirect_to :controller => 'users', :action => 'new_mother'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name, :surname, :dob, :location)
end
But then I need more controllers to perform this or a different action in the same controller called create_mother. But I tried everything with not success.
Can someone please help me (firstly) on the redirect_to method best practise, particularly if I need more controllers with same methods or the same controller with different methods, or the same controller same functions (I tried this and I am getting the error of "more redirections or renderings in a single function") and secondly if what is the best design to follow for this particular situation where I need exactly the same fields and actions but in different tables having different redirections each time.
My routes are:
Rails.application.routes.draw do
# The first page providing information about the current user (novice genealogist).
get 'users/new'
# The rest of the pages asking information to form the tree.
get 'fathers/new_father'
get 'mothers/new_mother'
# TODO
# get 'users/new_grandfather_m'
# get 'users/new_grandmother_m'
# The input windows that the user lands on need to create a new record in the database.
get '/signup', to: 'users#new'
get '/new_father', to: 'fathers#new_father'
get '/new_mother', to: 'mothers#new_mother'
# TODO
# get '/new_grandfather_m', to: 'users#new'
# get '/new_grandfather_m', to: 'users#new'
# This page will serve as the tree showing information from the above input.
get '/tree' , to: 'users#show'
# Used to update the database by creating records with the above info.
post '/signup', to: 'users#create'
post '/new_father', to: 'fathers#create_father'
post '/new_mother', to: 'mothers#create_mother'
# TODO
# post '/new_grandfather_m', to: 'users#create'
# post '/new_grandmother_m', to: 'users#create'
# The database of our system.
resources :users
# The homepage.
root 'users#new'
end
The other actions are: (basically I had made new controllers for each relation)
class FatherController < ApplicationController
# Creates a new instance of the user object (not a record).
def new_father
#father = User.new
end
# Creates a new record in the database by filling the fields of the new object instance
# with data.
def create_father
#father = User.new(father_params)
if #father.save
#redirect_to #user
redirect_to :controller => 'mothers', :action => 'new_mother'
else
render 'new_father'
end
end
# A private method to pass the parameters in the new object instance.
private
def father_params
params.require(:user).permit(:name, :surname, :dob, :location)
end
# Extracts information from the database.
def show_father
#father = User.find(params[:id])
end
end
NOTE:
I don't have to make relations etc, the important thing is to come up with a really simple tree to show the data of the user and to learn rails therefore relations one to one, one to many, many to many are not important.
Thank you in advance.
You have two create actions which, following your description, reside in different controllers. Both try to redirect to the UserController though which is inconsistent with the previous. You should add your controllers and routes code to your question to remove ambiguity.
The new_father and new_mother are not the views of your controller but the actions. When you do something like this at the end of an action:
redirect_to :controller => 'users', :action => 'new_mother'
The browser gets a redirect status with the url it should visit next, something like this:
Location: http://localhost:3000/users/new_mother
the browser then makes a new request at that url, meaning that if you have your routes in place, your UserController's new_mother action will get executed. Your new_mother and new_father should be actions within your UserController unless you do some routing hacks. To reiterate, just in case, this happens as a completely new request, unrelated to the first one that returned the redirection status.
When you call redirect_to or render, Rails does not yet send the response to the browser, instead it marks its internal response structure with some data and goes on with the rest of your action's code. It is only when the complete action code has been executed, the response is sent back. By its design Rails complains if you call those methods more than once within your action as it treats this as a potential mistake in the action code.
On the design:
There are persons and relations in your domain. Persons have the same attributes, be it a user, father or mother or any other relative. Your domain does not dictate that persons need to be distributed among different tables. In fact, what you are mainly after is the relation type between family members. Mother, father, sister, nephew, etc. are relations between a pair of people. It would then make sense to present these relations as a model:
# simplistic representation
create_table "relations" do |t|
t.integer "to_person_id"
t.integer "is_person_id"
t.string "relation_type"
end
relation_type field would carry the kinship type as a string, e.g. 'father'.
Although this would be one proper way to build a full-scale genealogy tree application, it is also more complex. It would involve single-table has-many associations and recursive SQL queries, both of which are rather advanced topics.
There are existing gems, handling this kind of schema. Some of them are ancestry, acts_as_tree, acts-as-dag.
Having different models/tables for same data is not a best approach. I think you should only need 2 tables to handle all of it (Of course one table for questions).
One table should be users in which all of the users will be stored. Every user will has the reference to it's mother and it's father
It can be achieved by self join/inheritance.
Then you need a table for answers in this table all of the answers for all of the users will be stored. All of the answer whether they are for mother , or father , or grand parents.
You only need an extra column to differentiate the type of the answer whether it's for mother/himself/father or grand parents.
Every answer will belong to the user who gave that answer and will belong to the question
So basically here is a raw implementation for this schmea.
class User < ActiveRecord::Base
has_many :answers
belongs_to :mother , class_name: 'User' , foreign_key: 'mother_id' #This is telling the user belongs to a mother and it's stored in same table. And mother reference will be stored in mother_id column. So user table should have a column mother_id
belongs_to: father , class_name: 'User', foreign_key: 'father_id' #This is telling the user belongs to a father and it's stored in same table. And father reference will be stored in father_id column. So user table should have a column father_id
has_many :children ,->(user){ where("users.father_id ? or users.mother_id = ?",user.id)} #Every user can have many children. Whether it's a mother or a father
def grand_father_from_father_side
father && father.father
end
def grand_father_from_mother_side
mother && mother.father
end
#Similary you can get grammy like mother.mother
end
class Question < ActiceRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user
#There will be a question_for column in answer table which will tell you this answer was posted for which relation i-e Mother , Father or User Himself
end
now In user controller it's pretty straight forward. You only need a single view which will be called for every type of answer whether it's for user or his parents or his grand father. Simply put a hidden field in the form which will tell you the type of the answer and it will be stored in answer table.
So if user is answering for her mother it's value will be 'Mother' and so on.
Then you can filter all the user answer with '
user.answers.where(answer_type: 'Mother') this will return all the answers for a user which he answered for his mother.

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

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.

Rails: Canned/RESTful way for accessing data returned by a method of a model

In my app I have a User model which defines a history method that returns a list of Activity objects, showing the last N actions the user has carried out. The UserController#history method wires this with a view.
The code looks as follows:
class UserController < ApplicationController
def history
user = User.find(params[:id])
#history = user.history(20)
end
end
class User < ActiveRecord::Base
has_many :activities
def history(limit)
...
end
end
Naturally, I also added this line to my routes.rb file:
match '/user/:id/:action', :controller => 'user'
so now when I go to localhost:3000/user/8/history I see the history of user 8. Everything works fine.
Being a Rails NOOB I was wondering whether there is some canned solution for this situation which can simplify the code. I mean, if /user/8 is the RESTful way for accessing the page of User 8, is it possible to tell Rails that /user/8/history should show the data returned by invoking history() on User 8?
First of all the convention to name controllers is in the plural form unless it is only for a single resource, for example a session.
About the routes I believe you used the resources "helper" in your routes, what you can do is specify that the resource routes to users also has a member action to get the history like this
resources :users do
member do
get :history
end
end
I think there is no cleaner way to do this
You can check it here http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
As far as the rails standards are concerned, it is the correct way to show the history in your case. In rails controllers are suppose to be middle-ware of views and model, so defining an action history seems good to me.
And you can specify the routes in better way as:
resources :user do
get 'history', :on => :member #it will generate users/:id/history as url.
end

Same Controller/View for multiple resources

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.

Dynamic CMS like routes in ruby on rails

I want to create a CMS like site where the user starts off with a some generic pages, i.e.
homepage
about
contact
etc
and from there can add child pages dynamically, for example
homepage
articles
article1
something
something-else
article2
about
contact
etc
To achieve this I'm planning on using some kind of self-referential association like
class Page < ActiveRecord::Base
belongs_to :parent, :class_name => 'Page'
has_many :children, :class_name => 'Page'
end
The one thing I'm struggling with is the route generation. Because pages can be added on the fly I need to dynamically generate routes for these pages and there is no way of knowing how many levels deep a page may be nested
So if I start off with the homepage:
/
and then start adding pages i.e.
/articles/article1/something/something-else/another-thing
How can something like that be achieved with the rails routing model?
Once you have some way to generate the URL string for your Page records (and I'll leave that part up to you), you can just map every page in config/routes.rb:
Page.all.each do |page|
map.connect page.url, :controller => 'pages', :action => 'show', :id => page
end
And have an observer hook the page model to reload routes when something changes:
class PageObserver < ActiveRecord::Observer
def reload_routes(page)
ActionController::Routing::Routes.reload!
end
alias_method :after_save, :reload_routes
alias_method :after_destroy, :reload_routes
end
Don't forget to edit config/environment.rb to load the observer:
# Activate observers that should always be running
config.active_record.observers = :page_observer
One solution to this prob is to dynamically load routes from hooks on your models. From example, a snippet from the Slug model on my site:
class Slug < ActiveRecord::Base
belongs_to :navigable
validates_presence_of :name, :navigable_id
validates_uniqueness_of :name
after_save :update_route
def add_route
new_route = ActionController::Routing::Routes.builder.build(name, route_options)
ActionController::Routing::Routes.routes.insert(0, new_route)
end
def remove_route
ActionController::Routing::Routes.routes.reject! { |r| r.instance_variable_get(:#requirements)[:slug_id] == id }
end
def update_route
remove_route
add_route
end
def route_options
#route_options ||= { :controller => navigable.controller,
:action => navigable.action,
:navigable_id => navigable_id,
:slug_id => id }
end
end
This inserts the route at top priority (0 in the routing array in memory) after it has been saved.
Also, it sounds like you should be using a tree management plugin and like awesome nested set or better nested set to manage the tree for your site.
You have to parse the route yourself
map.connect '*url', :controller => 'pages', :action => 'show'
Now you should have a params[:url] available in your action that is the request path as an array separated by the slashes. Once you have those strings its a simple matter to find the models you need from there.
That was from memory, and it's been a long while. Hope it works for you.
Look at RadiantCMS sources, they implement that functionality as far as i understand their self description.
I've implemented a similar functionality into a Rails gem, using self referential associations and a tree like js interface for reordering and nesting the "pages".
Templating language and authentication/authorization are left for the developer to implement.
https://github.com/maca/tiny_cms

Resources