I have a model that looks like:
class Product < ActiveRecord::Base
before_create :generate_token
def to_param
token
end
private
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(10, false)
break random_token unless Product.exists?(token: random_token)
end
end
end
My controller looks like:
def set_product
#product = Product.find_by(token: params[:token])
end
When I create a new product, I get the error:
NoMethodError in ProductsController#show
undefined method `name' for nil:NilClass
and my show in my controller looks like:
def show
#title = #product.name
end
my create looks like:
def create
#product = Product.new(product_params)
##product.user_id = current_user.id
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render action: 'show', status: :created, location: #product }
else
format.html { render action: 'new' }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
Also, in my controller I have a callback that looks like:
before_action :set_product, only: [:show, :edit, :update, :destroy]
But in the rails console insert, it shows the name being inserted. the name column is being set.
Can you show me your url that goes to that page
if you are passing the token by product_path(token)
then try
def set_product
#product = Product.find_by(token: params[:id]) # id instead of token
end
if it is a form then
def set_product
#product = Product.find_by(token: params[:product][:token])
end
I am prettry sure your current params[:token] is not right that is why you cannot find the product.
Related
I am following Agile Web Development with Rails 4. Chapter Cart 9 Cart Creation. When I want to update a cart, I get the following Error Notification: When assigning attributes, you must pass a hash as an arguments. CartController#update.
class CartsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:show, :edit, :update, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def index
#carts = Cart.all
end
def show
end
def new
#cart = Cart.new
end
def edit
end
def create
#cart = Cart.new(cart_params)
respond_to do |format|
if #cart.save
format.html { redirect_to #cart, notice: 'Cart was successfully created.' }
format.json { render :show, status: :created, location: #cart }
else
format.html { render :new }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
def update
#cart = Cart.find(params[:id])
respond_to do |format|
if #cart.update_attributes(params[:cart])
format.html { redirect_to #cart, notice: 'Cart was successfully updated.' }
format.json { render :show, status: :ok, location: #cart }
else
format.html { render :edit }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
def destroy
#cart.destroy if #cart.id == session[:card_id]
session[:card_id] = nil
respond_to do |format|
format.html { redirect_to store_url, notice: 'Your cart is currently empty.' }
format.json { head :no_content }
end
end
private
def set_cart
#cart = Cart.find(params[:id])
end
def cart_params
params[:cart]
end
def invalid_cart
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to store_url, notice: 'Invalid cart'
end
end
Your params is probably an instance of ActionController::Parameters
If so, you need to permit the attributes you want to use, like so:
def cart_params
params.require(:cart).permit(:attribute1, :attribute2, :attribute3)
end
Try this:
In your update method replace
if #cart.update_attributes(params[:cart])
by
if #cart.update_attributes(cart_params)
In your cart_params private method do this:
def cart_params
params.require(:cart).permit(:attribute1, :attribute2, :attribute3)
end
With Rails 4, the concept of strong parameters has been introduced which basically forbids mass assignment of attributes in the controller. This means that the mass assingment protection that was once in model (attr_accessible) has now been moved to the controller. Hence in your models you no more need to use this:
attr_accessible :attribute1, attribute 2 #attributes that can be mass-assinged
attr_protected :attribute3 #attribute that is protected and cannot be mass-assinged
Instead you can now do this in your controller through :
params.require(:cart).permit(:attribute1, :attribute2, :attribute3)
This means that only attribute1, attribute2. attribute3 of cart are accessible while other are protected attributes
In my app I have a commenting system that's largely based off of this railscast. Now in my models I'm changing the to_param to a random string so the id isn't in the url. But then that breaks commenting.
status.rb
class Status < ActiveRecord::Base
attr_accessible :content, :member_id, :document_attributes, :permalink
belongs_to :member
belongs_to :document
has_many :comments, as: :commentable, dependent: :destroy
before_create :make_it_permalink
accepts_nested_attributes_for :document
def to_param
permalink
end
private
def make_it_permalink
# this can create permalink with random 12 digit alphanumeric
self.permalink = SecureRandom.hex(12)
end
end
statuses_controller.rb
class StatusesController < ApplicationController
before_filter :authenticate_member!, only: [:index, :new, :create, :destroy]
before_filter :find_member
rescue_from ActiveRecord::RecordNotFound do
render file: 'public/404', status: 404, formats: [:html]
end
def index
#statuses = Status.order('created_at desc').page(params[:page]).per_page(21)
respond_to do |format|
format.html # index.html.erb
format.js
end
end
def show
#status = Status.find_by_permalink(params[:id])
#commentable = #status
#comments = #commentable.comments.order('created_at desc').page(params[:page]).per_page(15)
#comment = #commentable.comments.new
respond_to do |format|
format.html # show.html.erb
format.json { redirect_to profile_path(current_member) }
end
end
def new
#status = Status.new
#status.build_document
respond_to do |format|
format.html # new.html.erb
format.json { render json: #status }
format.js
end
end
def create
#status = current_member.statuses.new(params[:status])
respond_to do |format|
if #status.save
#activity = current_member.create_activity(#status, 'created')
format.html { redirect_to :back }
format.json
format.js
else
format.html { redirect_to profile_path(current_member), alert: 'Post wasn\'t created. Please try again and ensure image attchments are under 10Mbs.' }
format.json { render json: #status.errors, status: :unprocessable_entity }
format.js
end
end
end
def destroy
#status = current_member.statuses.find(params[:id])
#activity = Activity.find_by_targetable_id(params[:id])
#commentable = #status
#comments = #commentable.comments
if #activity
#activity.destroy
end
if #comments
#comments.destroy
end
#status.destroy
respond_to do |format|
format.html { redirect_to profile_path(current_member) }
format.json { head :no_content }
end
end
private
def find_member
#member = Member.find_by_user_name(params[:user_name])
end
def find_status
#status = current_member.statuses.find_by_permalink(params[:id])
end
end
comments_controller.rb
class CommentsController < ApplicationController
before_filter :authenticate_member!
before_filter :load_commentable
before_filter :find_member
def index
redirect_to root_path
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(params[:comment])
#comments = #commentable.comments.order('created_at desc').page(params[:page]).per_page(15)
#comment.member = current_member
respond_to do |format|
if #comment.save
format.html { redirect_to :back }
format.json
format.js
else
format.html { redirect_to :back }
format.json
format.js
end
end
end
def destroy
#comment = Comment.find(params[:id])
respond_to do |format|
if #comment.member == current_member || #commentable.member == current_member
#comment.destroy
format.html { redirect_to :back }
format.json
format.js
else
format.html { redirect_to :back, alert: 'You can\'t delete this comment.' }
format.json
format.js
end
end
end
private
# def load_commentable
# resource, id = request.path.split('/')[1,2] # photos/1/
# #commentable = resource.singularize.classify.constantize.find(id) # Photo.find(1)
# end
# alternative option:
def load_commentable
klass = [Status, Medium, Project, Event, Listing].detect { |c| params["#{c.name.underscore}_id"] }
#commentable = klass.find(params["#{klass.name.underscore}_id"])
end
#def load_commentable
# #commentable = params[:commentable_type].camelize.constantize.find(params[:commentable_id])
#end
def find_member
#member = Member.find_by_user_name(params[:user_name])
end
end
The problem lies in the load_commentable method in the comments_controller. I've tried a couple different variations of the method but the second one works best for my app and it was working when the url's had their id's in them. But since I overwrote the to_param to use my random permalink commenting stopped working because it's trying to find theid where it equals the permalink. Since it seems to try to find the id through the url, how do I pass the the actual id and not the permalink or how do I find commentable by it's permalink instead of id?
It's hard to tell if your param will always be the value of id or always be the permalink, or will sometimes be an id and sometimes a permalink.
If it will always be a permalink, then do:
#commentable = klass.find_by_permalink(params["#{klass.name.underscore}_id"])
instead of
#commentable = klass.find(params["#{klass.name.underscore}_id"])
If it is sometimes id and sometimes other, then you will need make logic to determine which is needed based on the class.
I am working on a mock-up of reddit where I submit url & title in a form and then the database should get populated and we should be taken to a show page to see our link and title that we submitted.
My questions is why I have to include this in my controller.
def show
#link = Link.find(params[:id])
end
If I delete the #link = Link.find(params[:id]) my show doesnt work.
I get the following error:
NoMethodError in Links#show
On this line
<%= #link.title %>
In addition to this I already have this private method:
private
def set_link
#link = Link.find(params[:id])
end
def link_params
params.require(:link).permit(:title, :url)
end
I was comparing to another project where I generated a scaffold for something similar and I only had the private method and no need for #link = Link.find(params[:id]) in my show action.
Here is my full controller code:
class LinksController < ApplicationController
def index
#link = Link.all
end
def new
#link = Link.new
end
def create
#link = Link.new(link_params)
respond_to do |format|
if #link.save
format.html { redirect_to #link, notice: 'Link was successfully created.' }
format.json { render action: 'show', status: :created, location: #link }
else
format.html { render action: 'new' }
format.json { render json: #link.errors, status: :unprocessable_entity }
end
end
end
def show
#link = Link.find(params[:id])
end
private
def set_link
#link = Link.find(params[:id])
end
def link_params
params.require(:link).permit(:title, :url)
end
end
And here is my full controller for the generated scaffold:
class HighScoresController < ApplicationController
before_action :set_high_score, only: [:show, :edit, :update, :destroy]
# GET /high_scores
# GET /high_scores.json
def index
#high_scores = HighScore.all
end
# GET /high_scores/1
# GET /high_scores/1.json
def show
end
# GET /high_scores/new
def new
#high_score = HighScore.new
end
# GET /high_scores/1/edit
def edit
end
# POST /high_scores
# POST /high_scores.json
def create
#high_score = HighScore.new(high_score_params)
respond_to do |format|
if #high_score.save
format.html { redirect_to #high_score, notice: 'High score was successfully created.' }
format.json { render action: 'show', status: :created, location: #high_score }
else
format.html { render action: 'new' }
format.json { render json: #high_score.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /high_scores/1
# PATCH/PUT /high_scores/1.json
def update
respond_to do |format|
if #high_score.update(high_score_params)
format.html { redirect_to #high_score, notice: 'High score was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #high_score.errors, status: :unprocessable_entity }
end
end
end
# DELETE /high_scores/1
# DELETE /high_scores/1.json
def destroy
#high_score.destroy
respond_to do |format|
format.html { redirect_to high_scores_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_high_score
#high_score = HighScore.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def high_score_params
params.require(:high_score).permit(:game, :score)
end
end
Why does the generated scaffold work without the #link = Link.find(params[:id]) in my show action while my own project won't?
You're missing the first line within the controller class of the generated scaffold:
class HighScoresController < ApplicationController
before_action :set_high_score, only: [:show, :edit, :update, :destroy]
You need to add a before_action to your controller in order to make your set_link function run:
class LinksController < ApplicationController
before_action :set_link, only: [:show]
Before the show action, call the set_link function.
You need to set the value of instance variable #link as
#link = Link.find(params[:id])
in show action because in show View of link, you would be displaying details of a particular Link object(#link) like
<%= #link.title%>
In order to do this the #link variable should be set as a Link object else you get an error.
As you already have a set_link method defined in the controller, you can reuse it across various actions using before_action.
class LinksController < ApplicationController
before_action :set_link, only: [:show, :edit, :update, :destroy]
....
....
end
and remove the calls to #link = Link.find(params[:id]) from show, edit, update and destroy actions( if any).
Setting up before_action with the only option would result in set_link method being called before the listed action is executed.
For eg: If you visit show url for a link then, firstly set_link would be called and after that show action(as show action is in the :only list)
Notice that the same thing (before_action :set_high_score) is setup for HighScoresController which is why redundant calls to #highscore = HighScore.find(params[:id] are avoided.
Users can create guides only when they're logged in.
When I click on the 'New Guide' link, this is what Heroku's log puts out:
2013-12-30T20:28:37.826032+00:00 app[web.1]: ActiveRecord::UnknownAttributeError (unknown attribute: user_id):
GuidesController:
class GuidesController < ApplicationController
before_action :set_guide, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
# GET /guides
# GET /guides.json
def index
if params[:tag]
#guides = Guide.tagged_with(params[:tag])
else
#guides = Guide.all
end
end
# GET /guides/1
# GET /guides/1.json
def show
end
# GET /guides/new
def new
#guide = current_user.guides.build(guide_params)
end
# GET /guides/1/edit
def edit
end
# POST /guides
# POST /guides.json
def create
#guide = current_user.guides.build(guide_params)
respond_to do |format|
if #guide.save
format.html { redirect_to #guide, notice: 'Guide was successfully created.' }
format.json { render action: 'show', status: :created, location: #guide }
else
format.html { render action: 'new' }
format.json { render json: #guide.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /guides/1
# PATCH/PUT /guides/1.json
def update
respond_to do |format|
if #guide.update(guide_params)
format.html { redirect_to #guide, notice: 'Guide was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #guide.errors, status: :unprocessable_entity }
end
end
end
# DELETE /guides/1
# DELETE /guides/1.json
def destroy
#guide.destroy
respond_to do |format|
format.html { redirect_to guides_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_guide
#guide = Guide.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def guide_params
params.require(:guide).permit(:title, :author, :description, :link, :tag_list) if params[:guide]
end
end
You have this in your new action
def new
#guide = current_user.guides.build(guide_params)
end
Why? The new action should just return the form to the browser to create a new guide. You repeat this in your create action, where it should be.
Also your index has this:
def index
if params[:tag]
#guides = Guide.tagged_with(params[:tag])
else
#guides = Guide.all
end
end
You should probably be using guide_params[:tag] since the :tag is being returned by the browser.
EDIT I see you are using [:tag_list] in your whitelist. I assume you are handing that somewhere else? Have you tested the ability to do an index action with a tag defined? I think the only place you want to use bare params[:xxxx] is in a private method.
I've created an activity model and I'm trying to have it so that when a user deletes their activity it also deletes the corresponding status. I've been able to do it when deleting the status, it deletes the activity but not sure how to do it in the opposite direction. I tried finding the status by targetable_id but I get:
undefined method `find_by_targetable_id' for #<Class:0x8df4a70>
Parameters:
{"_method"=>"delete",
"authenticity_token"=>"s2wKOZxCBVarT5uge3AIFNXHepFuvNGM+kU/q+ArOjA=",
"id"=>"18"}
If you're familiar with the public_activity gem then targetable is the same thing as trackable and in this example, the activity id is 18 and it's corresponding status id is 53
ActivitiesController
class ActivitiesController < ApplicationController
before_filter :authenticate_member!, only: [:destroy]
before_filter :find_activity, only: [:destroy]
def index
following_ids = current_member.following_members.map(&:id)
#activities = Activity.where("member_id in (?)", following_ids.push(current_member.id)).order("created_at desc").all
end
def destroy
#status = Activity.targetable
if #status
#status.destroy
end
#activity.destroy
respond_to do |format|
format.html { redirect_to :back }
format.json { head :no_content }
end
end
private
def find_activity
#activity = current_member.activities.find(params[:id])
end
end
StatusesController
class StatusesController < ApplicationController
before_filter :authenticate_member!, only: [:new, :create, :edit, :update, :destroy]
before_filter :find_member
before_filter :find_status, only: [:edit, :update, :destroy, :show]
rescue_from ActiveRecord::RecordNotFound do
render file: 'public/404', status: 404, formats: [:html]
end
# GET /statuses
# GET /statuses.json
def index
#statuses = Status.order('created_at desc').all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #statuses }
end
end
# GET /statuses/1
# GET /statuses/1.json
def show
respond_to do |format|
format.html # show.html.erb
format.json { redirect_to profile_path(current_member) }
end
end
# GET /statuses/new
# GET /statuses/new.json
def new
#status = Status.new
#status.build_document
respond_to do |format|
format.html # new.html.erb
format.json { render json: #status }
end
end
# GET /statuses/1/edit
def edit
end
# POST /statuses
# POST /statuses.json
def create
#status = current_member.statuses.new(params[:status])
respond_to do |format|
if #status.save
current_member.create_activity(#status, 'created')
format.html { redirect_to :back }
format.json
else
format.html { redirect_to profile_path(current_member), alert: 'Post wasn\'t created. Please try again and ensure image attchments are under 10Mbs.' }
format.json { render json: #status.errors, status: :unprocessable_entity }
end
end
end
# PUT /statuses/1
# PUT /statuses/1.json
def update
if params[:status] && params[:status].has_key?(:user_id)
params[:status].delete(:user_id)
end
respond_to do |format|
if #status.update_attributes(params[:status])
format.html { redirect_to profile_path(current_member), notice: 'Status was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #status.errors, status: :unprocessable_entity }
end
end
end
# DELETE /statuses/1
# DELETE /statuses/1.json
def destroy
#activity = Activity.find_by_targetable_id(params[:id])
if #activity
#activity.destroy
end
#status.destroy
respond_to do |format|
format.html { redirect_to :back }
format.json { head :no_content }
end
end
private
def find_member
#member = Member.find_by_user_name(params[:user_name])
end
def find_status
#status = current_member.statuses.find(params[:id])
end
def sortable_date
created_at
end
end
Should not this line
#status = Activity.targetable
be
#status = #activity.targetable
?
Second note: it probably will be better to move status destroying to Activity model before_destroy callback.