getting id from inside controller rails - ruby-on-rails

I'm new to rails so I don't know the correct terminology.
I'm trying to get an id of a community from my subscription controller but I'm getting the error undefined method 'community_id' for nil:NilClass
my SubscriptionController.rb:
class SubscriptionsController < ApplicationController
def create
#subscription = Subscription.new(subscription_params)
#subscription.account_id = current_account.id
#subscription.save
redirect_to community_path(#subcription.community_id) //error here
redirect_to community_path(params[:community_id]) // also error below
end
end
def subscription_params
params.require(:subscription).permit(:community_id)
end
error from 2nd line try: No route matches {:action=>"show", :controller=>"communities", :id=>nil}, missing required keys: [:id].
The subscription is saving into the db. What am I doing wrong?

You're making three common rookie misstakes here:
You're assuming that the community_id passed by the user is actually valid and corresponds with an existing record. Never trust user input.
You're assuming that creating the record will always succeed. It won't - and you should always code defensively to handle that condition. For example if the user double clicks the button the second request should fail as it would lead to a duplicate record.
You're redirecting twice. Each request can only have one response and what you would be doing is just overwriting the LOCATION header unless Rails prevented you from doing it with a double render error.
Instead I would do this as a nested route:
# config/routes.rb
resources :communities do
resources :subscriptions, only: [:create]
end
class Account < ApplicationRecord
has_many :subscriptions
# ...
end
class SubscriptionsController < ApplicationController
before_action :set_community, only: [:create]
# POST /communities/1/subscriptions
def create
# I'm assuming that you are ensuring the account is actually signed in your ApplicationController
# note that you don't need to use strong parameters here as you're not mass assigning any parameters
#subscription = current_account.subscriptions.new(
community: #community
)
# Ensure that the user is notified if creating the record fails.
if #subscription.save
redirect_to #community,
success: 'You are now subscribed.'
else
# #todo provide a more specific failure reason
redirect_to #community,
error: 'Could not create subscription. Please try again later.'
end
end
private
def set_community
# this will bail early and return a 404 response if an invalid id is passed.
#community = Community.find(params[:community_id])
end
end
# app/views/communities/show.html.erb
<%= button_to "Subscribe", [#community, :subscriptions], method: :post %>
This places the community_id parameter as part of the URL itself and is how you RESTfully model relations between resources. The relation is explicit instead of tucked away into the request body. If you're ever passing record ids as hidden inputs you're most likely doing it wrong.

Related

Anchor Tag Not Routing to Expected Page in Rails

I have an anchor tag, which when clicked I want it to open this route http://localhost:3000/shops/1 where 1 is the id of the seller_profile, however I am getting this error
No route matches {:action=>"show", :controller=>"seller_profiles", :id=>nil}, missing required keys: [:id]
<span class="icon"><%= image_tag "user.svg"%></span><span>My Profile</span>
class SellerProfile < ApplicationRecord
belongs_to :seller
end
class ApplicationController < ActionController::Base
helper_method :profile_of_current_seller
def profile_of_current_seller
#profile_of_current_seller ||= Shop.find(seller_id:session[:seller_id]) if session[:seller_id]
end
end
resources :sellers do
resources :seller_profiles, shallow: true
end
class SellerProfilesController < ApplicationController
before_action :set_seller_profile, only: [:show, :edit, :update, :destroy, :index]
# GET /seller_profiles/1
# GET /seller_profiles/1.json
def show
end
private
# Use callbacks to share common setup or constraints between actions.
def set_seller
#seller = current_seller
end
def set_seller_profile
#seller_profile = SellerProfile.find(params[:id])
end
end
You're getting this error because #profile_of_current_seller is nil. In the above scenario seller_profile_path(#profile_of_current_seller) is the same as seller_profile_path(nil). You would want to pass seller_profile_path an instance of seller_profile.
Assuming you have access to #seller_profile, amend your anchor tag to the following:
<span class="icon"><%= image_tag "user.svg"%></span><span>My Profile</span>
#profile_of_current_seller ||= Shop.find(seller_id:session[:seller_id]) if session[:seller_id]
This code would cause a different error than what you are receiving if session[:seller_id] was present. Something like "Couldn't find Shop with id=". You should use find_by instead of find. Find only allows you to look up records by id.
#profile_of_current_seller ||= Shop.find_by(seller_id:session[:seller_id]) if session[:seller_id]
So, because you are not receiving the "Couldn't find Shop with id=" error, the issue with your link is that session[:seller_id] (and therefore, #profile_of_current_seller) is nil.
Also, in your controller you are using params[:id] to set the seller profile, but are passing the id of #profile_of_current_seller, which is an instance of Shop...so Jeremy's Answer may help you out with that.

Rails- redirect_to carrying over unwanted id

I am trying to redirect from a show action to a custom collection action, but the id param is being carried over, causing the routing to fail. A minimal example:
routes.rb:
resources :first_models, only: [:show]
resources :second_models do
get 'custom_action', on: :collection
end
first_models_controller.rb
class FirstModelsController < ApplicationController
def show
redirect_to controller: 'SecondModelsController', action: 'custom_action'
end
end
second_models_controller.rb
class SecondModelsController < ApplicationController
def custom_action
# Do something
end
end
After setting that up, navigating to /first_models/2 results in an error:
No route matches {:action=>"custom_action", :controller=>"SecondModelsController", :id=>"2"}
I cannot figure out how to strip out the id param from the original request so that the routing matches.
The reason why this happens is that you call redirect_to with a Hash argument. Internally Rails uses url_for to build the final location, which in turn uses default_url_options which uses the ID of the current resource. From the API docs:
Missing routes keys may be filled in from the current request's parameters (e.g. :controller, :action, :id and any other parameters that are placed in the path).
See: http://api.rubyonrails.org/v5.1/classes/ActionDispatch/Routing/UrlFor.html
Solution: Use a named path helper.
Run bundle exec rake routes on the command line to get a list of all your routes and named path helpers. Pick the one that you need and use it as follows:
redirect_to my_named_path_helper_path
It is not the param the problem:
class FirstModelsController < ApplicationController
def show
redirect_to controller: 'second_models', action: 'custom_action'
end
end
You can type rails routes and see all your routes and how rails recognize them.
This should work. However you can be more explicit and use:
redirect_to custom_action_second_models_path

How to avoid undefined method error

I want to check if a current object's user-id is the same as the id of the current user, so I can allow some operations only to logged-in users. I am using the Devise gem to help me with authentication.
That said, I want to ask a question with a broader scope. I have build associations, at least I think so, but when I open the corresponding pages in the browser I get the error:
undefined method 'user' for nil:NilClass
I know that this error often happens when a particular object in the database is not instantiated or has no entries, but I am using the console and a PostgreSQL GUI tool, to check if the data is present.
This is a screenshot https://www.evernote.com/shard/s233/sh/305c5194-87e0-4019-9eba-9a7f5d7a2839/7c89b4842cc6efc1/res/b7879832-7829-4fe3-b81a-386b6f81cc11/skitch.png?resizeSmall&width=832
First to clarify that I understand right, here's what some things do:
If you define a method (def x) within a controller's "private" section this means, that the data is only available within your controller?
With a callback (before_action) you populate your app's REST methods with the data of the private method, it might want to use?
Now I have an image model with:
class Image < ActiveRecord::Base
mount_uploader :image, ImageUploader
belongs_to :user
belongs_to :game, inverse_of: :images
end
The user model reads like this:
class User < ActiveRecord::Base
...
has_many :images
has_many :games
validates :first_name, :last_name, presence: true
end
In the corresponding image controller I use:
class ImagesController < ApplicationController
before_action :set_image, only: [:show, :edit, :update, :destroy]
before_action :set_game
before_action :authenticate_user!
before_action :check_user
...
private
def set_image
#image = Image.find(params[:id])
end
def set_game
#game = Game.all
end
def check_user
unless (#image.user == current_user) || (current_user.admin?)
redirect_to root_url, alert: "Sorry but you are not allowed to visit this page."
end
end
def image_params
params.require(:image).permit(:title, :alt, :desc, :image, :category)
end
end
With #image.user in the check_user method I try to get the user's id. If I only use the current_user.admin? it works but not as intended, obviously.
As you can see in the screenshot above, the user_id field is populated, so I have no idea why I get this error. Maybe I forgot something?
Based on you error message, the problem is on #image.user in check_user method. Here, #image is nil. You should check if #image.nil? there.
Probably change it to:
#image = Image.find(params[:id])
unless !#image.nil? && ((#image.user == current_user) || (current_user.admin?))
BTW, you should only check user in :show, :edit, :update, :destroy like:
before_action :check_user, only: [:show, :edit, :update, :destroy]
What you're asking is something called authorization.
Authentication - does user exist?
Authorization - does user have permission?
Devise provides authentication, whilst authorization has no "standard" process for Rails.
What you're asking is the base line requirement for authorization in a Rails based application. The way to fix this is to use one of the authorization gems, namely CanCanCan or Pundit to ensure the user can change the required objects.
I'd personally set up authorization as follows:
#Gemfile
gem 'cancancan'
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Image, user_id: user.id
end
end
This will allow you to simply call can? :read, #image to validate the authorization for the user.
Fix
The real problem you have is that you're trying to call .user on a non-existent variable.
for nil:NilClass
When you see the above error, it means that you're calling a method on an undeclared variable.
Unlike other programming languages, Ruby doesn't so much treat the variable as undeclared, but as nil - confusing many developers. In short, the error means you're trying to call .user on a variable which does not have the method present; the solution being to ensure #image is declared.
-
The error seems to be caused by this:
#image.user #-> #image does not exist
Therefore, you have to check why #image has not been declared.
I would hazard a guess that the error is caused by your routes. You need to make sure you're calling the images controller properly:
#config/routes.rb
resources :images
#app/controllers/images_controller.rb
class ImagesController < ApplicationController
def show
#image = Image.find params[:id]
authorize! :read, #image
end
end
This should enable only users who own the image to view it. You'll not have to worry about authentication because that will be handled by Devise.
You can use respond_to? method to check if an object can respond to a particular method before calling it. Just like this.
object.respond_to?(:method_name)

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]

Resources