saving a model with multiple associations - ruby-on-rails

I have a model called theaters which stores info on theaters.
I list the theaters based on a zipcode searched for by the user. The user can then click on an individual theater and show reviews for it. If they are logged in they can add a review.
So my models are:
Theaters has_many reviews
User has many reviews
Reviews belongs to user and has one theater
When submitting the new review I am doing this (#theater_id is passed as a param via the add review submit button):
def create
#user_id = current_user
#review = Review.create(review_params.merge(:user_id => #user_id,:theater_id => #theater_id))
if #review.save
redirect_to #review
else
render 'edit'
end
end
I feel like because of my associations there is an easier more rails-correct way to do this. Like rails should automagically put in my current user id and theater id... right?
Thanks

Nested Resources
I don't know how your routes are set up, but this is typically within the realm of nested resources, where your reviews objects will be created under theatre objects:
#config/routes.rb
resources :theatres do
resources :reviews #-> domain.com/theatres/1/reviews/new
end
When you use routing like this, it sets an extra param in your controller, called theatre_id, which you can use to populate your new Review object:
#app/controllers/reviews_controller.rb
Class ReviewsController < ApplicationController
def create
#review = Review.new(review_params)
#review.save
end
private
def review_params
params.require(:review).permit(:your, :params).merge(theatre_id: params[:theatre_id]
end
end
--
Alternatively, you could set the theatre object of your new ActiveRecord object:
#app/controllers/reviews_controller.rb
Class ReviewsController < ApplicationController
def create
#theatre = Theatre.find params[:theatre_id]
#review = Review.new(review_params)
#review.theatre = #theatre
#review.save
end
end

Related

Nested routes with devise

So currently im trying to make an app where each user has its own todo list with its own index page, which means, everybody is able to visit the user page to see each tasks of the user.
I use devise and created a simple model with a user reference:
rails g model Todo title:string completed:boolean user:references
added of course belongs_to / has_many to todo.rb/user.rb
Now since i want each todo index page to be assiociated with the users todos, i've created a nested resource like so:
resources :users do
resources :todos
end
visiting
http://localhost:3000/users/1/todos/
works fine and shows the index page.
Heres the problem: when i change the number after /users/ to, for example, 2, its still working, even though there is no user with the id of 2.
Any ideas how i can make this dynamic, so that the integer after /users/ represents the user_id? Thought i did it right but i guess im missing something. Thanks in advance!
EDIT:
as requested, TodosController.rb:
class TodosController < ApplicationController
def index
#todos = Todo.all
end
def new
end
def show
#todo = Todo.find(params[:id])
#user = #todo.user
end
def update
end
def edit
end
end
Let look at your index action:
def index
#todos = Todo.all
end
It displays all todos always, because it doesn't know anything about the user.
It should be:
def index
#user = User.find(params[:user_id])
#todos = #user.todos
end
And in the show action you have to find the user at first, in this case you're sure, that the requested todo belongs to the requested user
def show
#user = User.find(params[:user_id])
#todo = #user.todos.find(params[:id])
end
You can refactor out #user = User.find(params[:user_id]) to the before_action callback, because you'll use it in all actions

Rails Form Associations - Passing id From Previous Page

Trying to figure our how to set up associations in form.
I have 3 models:
class Request < ActiveRecord::Base
has many :answers
has many :users, through: :answers
end
class Answer < ActiveRecord::Base
belongs to :user
belongs to :request
end
class User < ActiveRecord::Base
has many :answers
has many :requests, through: :answers
end
I am trying to figure out: how to have a User link to Answer#new from Request#Show, and then create an Answer record passing in the Request#Show request_id from the previous page - creating an association between the User's Answer and the Request he was viewing.
My method of doing this now is: I flash the request_id value on Request#Show, and then when a User links to Answer#new, it passes the flashed value into a hidden form tag on Answer#new. This does not seem like the best way to do this.
Any thoughts?
Kudos for the creative approach using flash, however your right there is an easy way. You can pass parameters much between controllers just like passing parameters between methods using the route names.
I didn't quite follow what it was you were trying to achieve in this case but it looks like this blog entry here should get you started..
https://agilewarrior.wordpress.com/2013/08/31/how-to-pass-parameters-as-part-of-the-url-in-rails/
Good luck!
User link to Answer#new from Request#Show
This can be achieved with either sessions or nested resources (or both!). Let me explain:
I would definitely add a nested resource to your requests routes:
#config/routes.rb
resources :requests do
resources :answers, only: [:new, :create] #-> url.com/requests/:request_id/answers [POST]
end
This gives you the ability to call a "nested" route (IE one which sends data to a child controller, and requires "parent" data to be appended to the request).
In your case, you want to create an answer for a request. The most efficient way is to use a routing structure as above; this will allow you to use the following controller method:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
#request = Request.find params[:request_id]
#answer = #request.answers.new
end
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.save
end
private
def answer_params
params.require(:answer).permit(:title, :body)
end
end
The above gives you the ability to create an answer by passing the request_id to the nested route. You must note the corresponding route will require a POST method in order to work.
You don't need the new method. If you wanted it, it can easily be handled with the above structure.
Passing the user is a little more tricky.
You can either use the routes, or set a session.
I would personally set a session (it's cleaner):
#app/controllers/requests_controller.rb
class RequestsController < ApplicationController
def show
session[:user_id] = #user.id #-> I don't know how you populate #user
end
end
This will give you the ability to access this session here:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
user = User.find session[:user_id]
end
end
#app/views/requests/show.html.erb
<%= link_to "New Answer", request_new_answer_path(request) %>
--
If you're using Devise, the user object should be available in the current_user object (which means you don't have to set session[:user_id]):
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
## current_user available here if using devise
end
end
To assign a #user to the new answer record, just do this in answers#create:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
...
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.user = current_user
#answer.save
end
end
Something like this worked for me:
I have two models (Formula and FormulaMaterial)
Formula has_many FormulaMaterials, which belongs to Formula
My Formula controller sets #formula like so:
#formula = Formula.find(params[:id])
I list my Formula Materials in my Formula show.html.erb by declaring it in my Formula controller like so:
#formula_materials = FormulaMaterial.where(:formula_id => #formula)
When I want to add a new FormulaMaterial to my Formula, the "New Formula Material" button in my show.html.erb file looks like this:
<%= link_to 'Add Material To Formula', new_formula_material_path(:formula_id => #formula), class: "btn btn-success" %>
In the "new_..._path" I set the associated id to the #formula variable. When it passes through to the new.html.erb for my FormulaMaterial, my URL looks like so:
http://localhost:3000/formula_materials/new?formula_id=2
In my FormulaMaterial new.html.erb file, I created a hidden_field that sets the value of the association by using "params" to access the formula_id in the URL like so:
params[:formula_id] %>
I am not sure if this is the best way to do this, but this way has allowed me to pass through the view id from the previous page as a hidden, associated and set field in the form every time.
Hope this helps!

Passing params[:id] to create method in Rails?

I'm trying to write a create method that collects the ID of the profile the user is currently viewing, along with some other information that is irrelevant to this question. However, because the create method POSTs rather than GETs (as I understand it), the value of params[:id] doesn't exist so it's always null. My code is as follows:
class PostsController < ApplicationController
def new
#Post = Post.new
end
def create
#Post = Post.new(post_params)
#Post.user_id = current_user.id
#Post.target_id = params[:id] #this
if #Post.save
redirect_to :back, notice: "You added a post!"
end
end
private
def post_params
params.require(:post).permit(:body)
end
end
Is there a way to get the value of params[:id] from elsewhere, perhaps from my Users controller in the show method where it actually exists?
Keep in mind that I was successfully able to create a hidden field in the Posts form, but I didn't like the fact that users were able to edit the value using Developer Tools, allowing them to change what profile the post would go to.
If there is a direct relation between the Target and the Post model, you should express this in the controller and model structure: link
This expresses your intention and it provides all the rails automations like routing, url helpers, form helpers, a.s.o.
In your concrete example, my guess is the Target would have many Posts:
class Target < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :target
end
Which would lead to the following route structure:
resources :targets do
resources :posts
end
To create a new post for the current target you would post to:
targets/:target_id/posts
And the target id would be accessed via params[:target_id]

Rails - Associated model's show action & its corresponding view?

I'm building a simple app that has a typical User model & a Profile model. User has_one Profile and Profile belongs_to User. All seems to be working fairly well as I am basically following the Michael Hartl tutorial. However, when I try to render the view of something from the profiles table (show action), I get an error (no id) AND the profile record I created gets deleted!
Questions:
In my ProfilesController, am I defining my show action properly for the simple view I am trying to render?
Why does simply visiting the url localhost/3000/profiles/1 delete the profile record? I think it has something to do with dependent destroy (b/c removing that will stop this behavior), but I think I want to keep dependent destroy, correct?
Routes
resources :users
resources :profiles
Models
Class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
Class Profile < ActiveRecord::Base
belongs_to :user
ProfilesController
def new
#profile = current_user.build_profile
end
def create
#profile = current_user.build_profile(params[:profile])
if #profile.save
flash[:success] = "Profile created dude!"
redirect_to root_path
else
render 'new'
end
end
def show
#profile = Profile.find(params[:user_id])
end
View (profiles/show.html.erb)
<p>Display Name: <%= #profile.display_name %></p>
Check your rake routes. You will see that for your Profile#show, you have URL structure like: /profiles/show/:id.
Thus, params, must be expecting the :id instead of :user_id.
If by /profiles/show/3, you wish to show profile 3, then:
def show
#profile = Profile.find(params[:id])
end

Create new object from nested resources for the new action method

Suppose I have this association:
class User < ActiveRecord :: Base
has_one :car
end
class Car < ActiveRecord :: Base
belongs_to :user
end
routes:
resources :users do
resources :cars
end
Then what would be the code, in the 'new' action method in CarsController
class CarsController < ApplicationController
def new
#??? what's necessary to be put here,
# if I have request 'localhost:3000/users/1/cars/new'
end
...
end
Will Rails figure out everything automatically so I don't have to write any code in the 'new' method? Also, since the 'new' action will generate a 'form_for(#car)' form helper, how can I create this car object
Is this right?
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.build_car({})
end
end
That looks just fine. Rails will not do any of this automatically. There are gems out there that can automate some of that if you like, but the jury is out on whether they're actually worth your time.
If you have no params to pass to the car, you can just run #user.build_car with no arguments, by the way. You'll also need to specifically say in the form_for helper that you're nesting the car under the user: form_for [#user, #car] Otherwise the form's destination will be /cars instead of /user/1/cars.
You're pretty close, Baboon. I would actually do:
class UsersController < ApplicationController
def new
#user = User.new
#car = #user.cars.build
end
end
But if you don't want to create the Car at the same time as the user, try:
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.cars.build
end
end
I found the nested_form railscast extremely helpful when doing this sort of thing. In fact, I think you'll probably get a lot out of it (and using the nested_form gem).
railscasts_196-nested-model-form-part-1

Resources