nested models best practice - rails - ruby-on-rails

Suppose I have two models model1 and model2, and that model2 belongs_to model1 (conversely, model1 has many model2). Suppose now I want to create a model2, from the model1/1 page view, (the page showing the model1 with id 1). Here's what I did :
<%= form_for(#model2, remote: true) do |f| %>
<%= f.text_field :title %>
<%= f.submit "POST" %>
<% end %>
(#model2 was instantiated in the model1 controller show method). Is this a best practice ? Should I use nested attributes ?

What CDub said is right. However you can achieve the nested CRUD resources this way:
user = model1
post = model2
class user < ActiveRecord::Base
has_many :posts
end
class post < ActiveRecord::Base
belongs_to :user
end
In your routes you can do this:
routes.rb
resources :users do
resources :posts
end
and in your posts controller you can do this:
class UsersController < ApplicationController
def new
#post = current_user.posts.new
end
def create
#post = current_user.posts.new(params[:post])
if #post.save
redirect_to user_posts_path(current_user, #post)
else
render :new
end
end
end
You can trigger this route by doing:
<%= link_to 'new post', new_user_post_path(current_user) %>
and edit:
<%= link_to 'edit post', edit_user_post_path(current_user, #post) %>
checkout: nested resources rails api

I don't know about the best practices, but I think it makes the most sense to try and only CRUD models within their resource scope. That said, I prefer to use accepts_nested_attributes_for and creating it through a form submission to #model1, but again, it's simply preference - either will work.

Related

Where to handle request that deal with multiple models

Example: a Survey has many Questions. A request is made to create a survey, and the request contains the survey title and metadata, plus all the questions.
This all happens on one page -- when the user clicks submit, the survey and its questions are created.
So far I have all the logic in the SurveysController but I'm not sure if this is MVC, especially because I have methods like add_question and remove_question.
Is there a preferred way of doing this?
If you're creating the questions through accepts_nested_attributes_for, then it would be okay.
--
When you mention add_question / remove question - this would be best handled in a separate questions controller (with nested resource routing):
#config/routes.rb
resources :surveys do
resources :questions, only: [:create, :destroy]
end
This allows you to use the following:
#app/controllers/surverys_controller.rb
class SurveysController < ApplicationController
def show
#survey = Survey.find params[:id]
#new_question = #survey.questions.new
end
end
#app/views/surveys/show.html.erb
<%= #survey.title %>
<% #survey.questions.each do |question| %>
<%= link_to "Remove", surveys_question_path(#survey, question), method: :delete %>
<% end %>
<%= form_for #question do |f| %>
<%= f.text_field :text %>
<%= f.submit %>
<% end %>
This would keep your controllers conventional.
Nested resources:
resources :surveys do
resources :questions # check http://localhost:3000/rails/info/routes for generated routes
end
Now you can create QuestionsController with normal CRUD actions and survey_id in params. I.e:
class QuestionsController
def create
#question = Question.new(survey_id: params[:survey_id], ... )
end
end
And to create survey with all questions at once use accepts_nested_attributes_for on Survey model http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

How can I implement a 'create' action without 'new' action

How can I best implement this feature: As an admin, I can assign a Resident Manager to a Hall.
I have a User model with a namespace routing for the admin -I intend on having another namespace routing that would hold the functions of the RM-
I have a Hall model.
Since its a many-many relationship between the above to models, I have a Management join model which contains only user_id and hall_id columns.
I know implementing the above feature, entails creating a new record in the management table but I don't know how to do it. I didn't think using a form (management#new) would solve this because the admin should not know the user_ids/hall_ids...
BELOW IS WHAT I HAVE TRIED TO DO BUT I CAN'T GET IT RIGHT
When the admin gets to the user index page, s/he should see a link for Hall Assignment for each user. This link leads to the management show page for that particular user, which would show the list of halls assigned to that user and also the show all the other remaining halls that isn't assigned to the user. So, either clicking an ADD button or on the hall's name should add it to that user's assigned halls list which is on the same page.
Management#show page
<h2><%= #user.email %>'s assigned halls</h2>
<% #user.managements.each do |management| %>
<%= management.hall.name %>
<% end %>
<p> HALL LISTS </p>
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add" %> </li>
<% end %>
</ul>
Here's is my Management controller
class Admin::ManagementsController < ApplicationController
def index
#managements = Management.all
end
def show
#user = User.find(params[:id])
#halls = Hall.all
end
def create
#management = Management.create(managements_params)
redirect_to admin_management_path
end
private
def managements_params
params.
require(:management).
permit(user_id: params[:user_id], hall_id: params[:hall_id])
end
end
And here's a piece of what my routes file looks like:
namespace :admin do
resources :users, only: [:index, :update]
resources :halls, only: [:index, :new, :create]
resources :managements, only: [:index, :new, :create, :show] do
resources :halls, only: [:index]
end
end
Your "add" button is just a mini form (with mostly hidden fields). You can instead just make it an actual form (with the submit-button having the text "Add") and the id-values filled in from the item on the page... it just points to the same routes that you'd normally point the form that you'd find in the new template.
if you want more detail, then show us the code that you have written (rather than a verbal description of it).
Edit:
Ok, so you'd put a button on the page like this
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add", managements_path(management: {user_id: #user.id, hall_id: hall.id}, method: :put ) %> </li>
<% end %>
</ul>
Notice the managements_path - you might need to check that that routing is correct (check it against what is in rake routes). Note that you're passing in the user id and the hall id, and that you must set the method to "put" on the button.
First things first -
How can I implement a 'create' action without 'new' action
It's relatively simple to do - you will need a create action somewhere, but a new action is just a way to build the respective ActiveRecord object for your controller.
If you do this in another action, you just have to make sure you point the form to the correct create action (and that create action to redirect back to
--
New / Create
Here's how you could handle the new / create actions in different controllers, as an example for you:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def index
#hall = Hall.new
end
end
#app/controllers/halls_controller.rb
Class HallsController < ApplicationController
def create
#hall = Hall.new hall_params
redirect_to users_path if #hall.save
end
private
def hall_params
params.require(:hall).(:hall, :attributes, :user_id)
end
end
This will allow you to show the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= form_for #hall, url: hall_path do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_field :x %>
<%= f.submit %>
<% end %>
<% end %>
--
Fix
ADD button or on the hall's name should add it to that user's assigned halls list
For this, I don't think you'd need a create action in the "traditional" sense - it will be more about adding new halls to a user's current halls. This is much different than creating a new hall itself:
#config/routes.rb
namespace :admin do
resources :users do
post "hall/:id", to: :add_all #=> domain.com/admin/users/:user_id/hall/:id
end
end
#app/controllers/admin/users_controller.rb
Class UsersController < ApplicationController
def add_hall
#user = User.find params[:user_id]
#hall = Hall.find params[:id]
#user.halls << #hall
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_halls
has_many :halls, through: :user_halls
end
#app/models/hall.rb
Class Hall < ActiveRecord::Base
has_many :user_halls
has_many :users, through: :user_halls
end
#app/models/user_hall.rb
Class UserHall < ActiveRecord::Base
belongs_to :user
belongs_to :hall
end
This uses the ActiveRecord collection methods to make this work, to which you'll be able to provide the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= button_to "Add Hall Test", user_add_hall_path(user, 1) %>
<% end %>

Rails: Commenting for a Post

I am creating a website blog and don't know what I'm going to do for adding a comment in a blog. Is there a gem or trick for that? Can you please help me and give me some tips?
There is no "trick" - you just have to code it up like the rest of your application.
Associations
Since your comments will be directly associated to your posts, it makes to start by looking at the association between the two models:
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :post
end
#app/models/post.rb
Class Post < ActiveRecord::Base
has_many :comments
end
The importance of this association is that if you want to create comments, although they'll be their own individual objects, they'll have to be "tied" to a post. The importance of this lies in the object-orientated nature of Ruby / Rails:
--
OOP
Most people don't realize that since Ruby is an object orientated language, Rails is also an object-orientated framework.
This means that you need to structure you methods, actions, etc around objects. Most beginners think you just have to create a "logical" app flow - whereas the reality is you need to build your app around the objects you hope to serve
--
Resources
Further to this, you then need to consider how you'll interact with the resources / objects in your application. I would recommend using nested resources to do this:
#config/routes.rb
resources :posts do
resources :comments #-> domain.com/posts/5/comments/new
end
The reason this is important is because of how you can then create a comment with it:
#app/controllers/comments_controller.rb
Class CommentsController < ApplicationController
def new
#post = Post.find params[:post_id]
#comment = Comment.new
end
def create
#post = Post.find params[:post_id]
#comment = Comment.new(comment_params)
end
private
def comment_params
params.require(:comment).permit(:comment, :params, :post_id)
end
end
This allows you to use the following:
#app/views/comments/new.html.erb
<%= form_for [#post, #comment] do |f| %>
<%= f.text_field :comment_attributes %>
<%= f.submit %>
<% end %>
This will help you create comment objects as a child of the post objects you want - giving you the ability to create comments for each post.
Bonus
A bonus here is that if you wanted to then nest the comments on your site, you'll want to use the Ancestry gem, as follows:
The way to do this is relatively simple. If you have a comments creation system set up, as outlined above, you'll want to add the ancestry gem to your Comment model:
#app/models/comments.rb
Class Comment < ActiveRecord::Base
has_ancestry
end
You'll need to migrate an ancestry column to your comments datatable, and then be able to populate the ancestry attribute:
You can then use the following partial to show the comments in a tree fashion:
#app/views/comments/_tree.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
</div>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
You can then call it as follows:
<%= render partial: "comments/tree", locals: { collecton: #comments } %>
You could implement external comments engine, such as Disqus, or keeping it in house by making a Comment resource, which would have a relation to your post, like
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
This way you have a right way of treating your comments.
It needs some other work, controllers, views etc... But the spirit is there.

Nested Models with Comments

I have an application where there are a few nested models... Two parent models and two child models.
I'm trying to create comments on the child models and I had it working great for the first one until I realized I have to create comments on the second child so I realized I had to scrap my work because I was targeting the first parent + child model in the comments controller. So I decided to watch Ryan Bates screencast (http://railscasts.com/episodes/154-polymorphic-association) on creating comments that belong to multiple models...unfortunately, it's not working for me and I'm assuming its because I am trying to create comments on the child models. I will show you what I was using before that worked for the one model and I'll show you what im doing now that doesnt work...
here is what i had for the comments controller
def create
#collection = Collection.find(params[:collection_id])
#design = #collection.designs.find(params[:design_id])
#comment = #design.comments.create(comment_params)
#comment.user = current_user
#comment.save
redirect_to collection_design_path(#collection, #design)
end
and here is what it is now after i tried to implement it to work for multiple models
def create
#commentable = find_commentable
#comment = #commentable.comments.build(comment_params)
#comment.user = current_user
#comment.save
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
here are my crazy routes
resources :collections do
member do
post :like
post :unlike
end
resources :designs do
resources :comments
member do
post :like
post :unlike
end
end
end
anyone have any other different ideas for created comments for multiple nested models? Thanks in advance for the help.
EDIT:
Here was the form I was using for the one model
<%= form_for([#collection, #design, #design.comments.build]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
and here is the one i'm using now
<%= form_for([#collection, #design, #commentable, Comment.new]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
Right now when I try to submit the new comment form I get this error
undefined method `comments' for #<Collection:0x0000010150cf88>
which points back to the create method
EDIT 2
Here is my comment model
belongs_to :commentable, :polymorphic => true
belongs_to :user
Here is my design model (which is a child of the collection model)
has_many :comments, :dependent => :destroy, :as => :commentable
belongs_to :user
belongs_to :collection
and my collection model (which has the child model: design)
belongs_to :user
has_many :designs, :dependent => :destroy
and there is more to the models but its not related to the problem.

Field for in rails 3 not showing displaying elements inside

I have a model named Order and another model named Member and when I try to display fields from the Members model in my Orders view it doesn't even show when using the fields_for tag. Heres what my code looks like.
order model
class Order < ActiveRecord::Base
has_many :members
end
member model
class Member < ActiveRecord::Base
belongs_to :order
end
orders controller
class OrdersController < ApplicationController
def new
#order = Order.new
3.times { #order.members.build }
#title = "Order Form"
end
def create
#order = Order.new params[:order]
if #order.save
flash[:notice] = "Your order has been created"
redirect_to orders_path
else
#title = "Order Form"
render 'new'
end
end
end
The issue is in my orders view:
<% for member in #order.members %>
This displays 3 times but the information below doesn't
<% fields_for "...", member do |member_form| %>
<p>
Name: <%= member_form.text_field :name %>
</p>
<% end %>
<% end %>
For some odd reason the information in the fields for tag won't even display once. Am I missing something?
If you find out what I am doing wrong, can you please explain it to me because I am new to rails.
Thanks in advance!
The block given to a fields_for call on a collection will be repeated for each instance in the collection, essentially creating its own loop, so you don't really need to write your own explicit loop "for member in #order.members". Further, you can leverage nested_attributes functionality to enable saving of associated members directly with #order.save:
class Order < ActiveRecord::Base
has_many :members
accepts_nested_attributes_for :members
end
class Member < ActiveRecord::Base
belongs_to :order
end
In the view:
<%= form_for #order do |order_form| %>
...
<%= order_form.fields_for :members do |member_form| %>
Name: <%= member_form.text_field :name %>
<% end %>
<% end %>
And I think your controller create method should work as you have it.
See the API docs for fields_for, especially the One-to-many subsection.
I think you just need to get rid of the "...", in your call to fields_for, as fields for is expecting an object.
Try:
<% fields_for member do |member_form| %>

Resources