Passing params[:id] to create method in Rails? - ruby-on-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]

Related

Using Pundit to resolve association scope

Example code:
#model
class Profile < AR:Base
has_many :projects
end
#controller
class ProfilesController < AC
def show
#user = Profile.find(params[:id])
end
end
#view
#profile.projects.each do |project|
= render something
end
Any user can view any profile, but projects should be filtered by visibility (like public/private projects).
I'm concerning to add one more ivar because it violates Sandi Metz's rule
Controllers can instantiate only one object. Therefore, views can only
know about one instance variable and views should only send messages
to that object (#object.collaborator.value is not allowed).
The only way I see it now is to introduce another class (facade) to do this things, like:
class ProfilePresenter
def initialize(profile, current_user)
#profile = profile
#current_user
end
def visible_profiles
ProjectPolicy::Scope.new(current_user, profile.projects).resolve
end
end
Am I missing something?
How would one achieve it (resolving association scopes) using Pundit?
In case we will need pagination for projects within profile view - what approach to choose?

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!

saving a model with multiple associations

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

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

Where do I put 'helper' methods?

In my Ruby on Rails app, I've got:
class AdminController < ApplicationController
def create
if request.post? and params[:role_data]
parse_role_data(params[:role_data])
end
end
end
and also
module AdminHelper
def parse_role_data(roledata)
...
end
end
Yet I get an error saying parse_role_data is not defined. What am I doing wrong?
Helpers are mostly used for complex output-related tasks, like making a HTML table for calendar out of a list of dates. Anything related to the business rules like parsing a file should go in the associated model, a possible example below:
class Admin < ActiveRecord::Base
def self.parse_role_data(roledata)
...
end
end
#Call in your controller like this
Admin.parse_role_data(roledata)
Also look into using (RESTful routes or the :conditions option)[http://api.rubyonrails.org/classes/ActionController/Routing.html] when making routes, instead of checking for request.post? in your controller.
Shouldn't you be accessing the parse_role_data through the AdminHelper?
Update 1: check this
http://www.johnyerhot.com/2008/01/10/rails-using-helpers-in-you-controller/
From the looks of if you're trying to create a UI for adding roles to users. I'm going to assume you have a UsersController already, so I would suggest adding a Role model and a RolesController. In your routes.rb you'd do something like:
map.resources :users do |u|
u.resources :roles
end
This will allow you to have a route like:
/users/3/roles
In your RolesController you'd do something like:
def create
#user = User.find_by_username(params[:user_id])
#role = #user.roles.build(params[:role])
if #role.valid?
#role.save!
redirect_to #user
else
render :action => 'new'
end
end
This will take the role params data from the form displayed in the new action and create a new role model for this user. Hopefully this is a good starting point for you.

Resources