Not able to save from data for a nested model - ruby-on-rails

I am new to rails and finishing up the Michael Hartl tutorial and creating some variations on the basic app from the book. One thing I am trying to do is create a set of model associations that is three deep (user-> colleciotn-> pictures ->). I am also trying to place my pictures form in the view for showing collections. I tried following the pattern used in the book for the User to collection relationship, but have had several issues. First, I could not use the Form_with tag (form_with(model: #picture, local: true)) and ended up writing out the path (form_with(url:"/pictures/create", method: "post")). Also, I used a hidden field tag to pass the collection_id to the "create" method.
My issue now seems to be that it is not saving the #picture data in the Picture Controller. Here is the line I think is suspect:
#picture= #collection.pictures.build
Here is a summary/my understanding of what I am trying to do.
render the Picture form on the Controller show page
Post the form date to the picture model, while also passing the
Controller object ID to the controller so to preserve the picture to controller relationship
Call the Controller object using the controller ID that was sent in
params
Save the Picture params to the Picture model with .build and flash a success message
From the logs, I believe the issue is with my use of the .build (highlighted below in code).
I will provide the code below for all of the elements of the app, as well as the log. I could really use some help figuring out what I am doing wrong. Let me know if there is anything else I should share.
Models
Picture Models
class Picture < ApplicationRecord
belongs_to :collection
validates :collection_id, presence: true
validates :picture_title, presence: true, length: { maximum: 30}
end
Collection Model
class Collection < ApplicationRecord
belongs_to :user
has_many :pictures, dependent: :destroy
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :collection_title, presence: true, length: { maximum: 30 }
end
User Model
class User < ApplicationRecord
has_many :collections, dependent: :destroy
has_many :pictures, through: :collections
attr_accessor :remember_token
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def forget
update_attribute(:remember_digest, nil)
end
def feed
Collection.where("user_id = ?", id)
end
end
Routes
Rails.application.routes.draw do
get 'sessions/new'
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
post '/pictures/create', to: 'pictures#create'
resources :users
resources :collections
resources :pictures
resources :users do
resources :collection
end
resources :collections do
resources :pictures
end
end
Picure Controller
def create
#collection = Collection.find(params[:collection_id])
#picture= #collection.pictures.build
if #picture.save!
flash[:notice] = "Picture was successfully added."
redirect_to request.referrer
else
flash[:alert] = "Picture could not be saved."
redirect_to request.referrer
end
end
private
def correct_user
#collection = current_user.collections.find_by(id: params[:id])
redirect_to root_url if #collection.nil?
end
def picture_params
params.require(:picture).permit(:picture_title)
end
end
Collections Controller
class CollectionsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy, :show, :index]
before_action :correct_user, only: [:destroy, :show]
def show
#collection = Collection.find(params[:id])
#picture= Picture.new
end
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
flash[:success] = "Image collection created!"
redirect_to root_url
else
#feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
end
end
def destroy
#collection.destroy
flash[:success] = "Collection deleted"
redirect_to request.referrer || root_url
end
private
def collection_params
params.require(:collection).permit(:collection_title)
end
def correct_user
#collection = current_user.collections.find_by(id: params[:id])
redirect_to root_url if #collection.nil?
end
end
**Picture Form **
<%= form_with(url:"/pictures/create", method: "post") do |f| %>
<div class="field">
<%= f.text_field :picture_title, placeholder: "Picture Title" %>
</div>
<%= f.submit "Create Collection", class: "btn btn-primary" %>
<%= hidden_field_tag :collection_id, #collection.id %>
<% end %>
Logs
Started POST "/pictures/create" for 99.150.231.55 at 2020-01-04 19:29:08 +0000
Cannot render console from 99.150.231.55! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by PicturesController#create as JS
Parameters: {"authenticity_token"=>"GNDEKiGPVP7EHRtgphGDMIJxbKgnXn2MFSmgTJMIoEo2Owan5THjMIx9N8pKLkS7hmaqJMdwhjqvuOBR/3JaHg==", "picture_title"=>"TEST", "collection_id"=>"10", "commit"=>"Create Collection"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:18:in `current_user'
Collection Load (0.1ms) SELECT "collections".* FROM "collections" WHERE "collections"."id" = ? ORDER BY "collections"."created_at" DESC LIMIT ? [["id", 10], ["LIMIT", 1]]
↳ app/controllers/pictures_controller.rb:12:in `create'
Completed 500 Internal Server Error in 6ms (ActiveRecord: 0.2ms | Allocations: 2423)
NoMethodError (undefined method `picture_title=' for nil:NilClass):
app/controllers/pictures_controller.rb:14:in `create'
EDIT
I implemented Max's code corrections, but now getitng the following error:
Started POST "/collections/10/pictures" for 99.150.231.55 at 2020-01-05 17:57:57 +0000
Cannot render console from 99.150.231.55! Allowed networks: 127.0.0.0/127.255.255.255, ::1
(0.1ms) SELECT sqlite_version(*)
NoMethodError (undefined method `make_response!' for PicturesController:Class):

A lot of things are off here. First axe this junk:
post '/pictures/create', to: 'pictures#create' # never do this again please.
The route here to create a picture is going to be POST /collections/:collection_id/pictures. Which RESTfully describes that we are creating a picture that belongs to a collection. You already have that route setup by:
resources :collections do
resources :pictures
end
In rails the action is only ever in the path for /edit and /new. All the other actions are defined by the HTTP verb.
class PicturesController < ApplicationController
before_action :set_collection, only: [:new, :create, :index]
# POST /collections/1/pictures
def create
#picture = #collection.pictures.new(picture_params)
if #picture.save
flash[:notice] = "Picture was successfully added."
redirect_to #collection
else
flash.now[:alert] = "Picture could not be saved."
render 'collections/show'
end
end
# ...
private
def set_collection
#collection = Collection.find(params[:collection_id])
end
def picture_params
params.require(:picture).permit(:picture_title)
end
end
Don't do redirect_to request.referrer when a record is not valid (or really at all). Many clients do not send the HTTP referer header and it will make for a really bad user experience as any user input and the validation messages are lost. Most of the time you actually know where you should send the user like in your Collections#destroy method which should probably redirect to the index or the users feed. If you really want redirect back reliably save the location in the session.
The form should read:
<%= form_with(model: [#collection, #picture]) do |f| %>
<div class="field">
<%= f.text_field :picture_title, placeholder: "Picture Title" %>
</div>
<%= f.submit %>
<% end %>
Since the collection id is in the path we don't need to do that hacky garbage of using a hidden input to pass it. This also binds the form to the model instance so that the user input is not lost if the input is invalid.

Related

Models associated with one another create undefined methods in Rails 5 undefined method `shoutouts' for #<User:0x007fd825472cf0>

Was using Michael Harti's Ruby on Rails Tutorial and when following it I get an undefined method between two models. I have renamed the models and tried playing around a lot with it but all attempts end up with undefined methods or an uninitialized constant.
Shout Out controller
class ShoutOutController < ApplicationController
def show
#user = User.find(params[:id])
end
def create
#shout_out = current_user.shout_out.build(shout_out_params)
if #shout_out.save
flash[:success] = "Shout Out Created"
redirect_to root_url
else
render user
end
end
def destroy
end
private
def shout_out_params
params.require(:shout_out).permit(:content)
end
end
User Controller
class UsersController < ApplicationController
def index
#users = User.paginate(page: params[:page])
end
def new
#user = User.new
end
def show
#user = User.find(params[:id])
#shoutouts = #user.shout_outs.paginate(page: params[:page])
#shoutouts.user = User.find(params[:id]).name
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
#handles successful update of account info
flash[:success] = "Updated Profile Info"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
user model
class User < ActiveRecord::Base
attr_accessor :remember_token
# :activation_token
has_many :scrimmages
has_many :ShoutOuts, dependent: :destroy
has_many :friendships
has_many :direct_messages, :through => :friendships
before_save :downcase_email
# before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: {with: VALID_EMAIL_REGEX}, uniqueness: { case_sensitive: false}
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#returns a random token for remember function
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
#returns true if token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
#forgets the user
def forget
update_attribute(:remember_digest, nil)
end
private
# converts emails to downcase
def downcase_email
self.email = email.downcase
end
#creates and assigns activation token and digest
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
micropost model
class ShoutOut < ApplicationRecord
belongs_to :user
default_scope -> { order(created_at: :desc)}
validates :user_id, presence: true
validates :content, presence: true, length: {maximum: 140}
end
view partial
<li id="shoutout-<%= ShoutOut.id %>">
<%= link_to gravatar_for(ShoutOut.user, size: 50), ShoutOut.user %>
<span class="user"><%= link_to ShoutOut.user.name, ShoutOut.user %></span>
<span class="content"><%= ShoutOut.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(shout_out.created_at) %> ago.
</span>
routes file
Rails.application.routes.draw do
# root to: 'users#new'
# these routes are for showing users a login form, logging them in, and logging them out.
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
get '/logout' => 'sessions#destroy'
get '/signup' => 'users#new'
post '/users' => 'users#create'
post '/users/id/edit' => 'users#edit'
resources :users
resources :account_activations, only: [:edit]
root to: 'landing#index'
end
Error Message Screenshot-
There are a couple of errors in the above code which is confusing rails.
In the micropost model, you have defined a class called ShoutOut. You may have heard the expression 'convention over configuration'. Rails is looking for the shoutout file. Your ShoutOut class should be in a file called shout_out.rb in the models folder.
In the user class, you have put has_many ShoutOuts, it should be has_many :shout_outs
I have a feeling the ShoutOutController should be ShoutOutsController, but you will need to post your routes file in order to confirm.
In the view partial, why are you calling ShoutOut.id? I suspect you want to use shout_out.id, but you need to post the view that is calling that partial, along with the controller method that is calling the view. In this view you currently only have access to #user variable which is set in the show method in the Users controller.
It looks like you are not clear on why some things should be capitalised and why they shouldn't. Read up on instance vs method variables and on class methods vs instance methods. Try the following in your view
<% #user.shoutouts.each do |shout_out| %>
<li id="shoutout-<%= shout_out.id %>">
<%= link_to gravatar_for(#user, size: 50), #user %>
<span class="user"><%= link_to #user.name, #user %></span>
<span class="content"><%= shout_out.content %></span>
<span class="timestamp"> Posted <%= time_ago_in_words(shout_out.created_at) %> ago. </span>
</li>
<% end %>
As Joe C commented, you need to post the actual error message along with the stack trace so we can help you debug the error. If you look carefully at the error message, it will give you a file name and line number of where the error is occurring.
Ty again for helping, but what I've been using is the guide from michael harti linked above and, after a lot of changes this was when i decided to post on Stack. I tried using User.shout_out and User.shoutout and User.shout_outs with the results being the same error. Is the guide wrong?

Wicked Gem with cocoon gem and devise user model

So I have a User model generated with devise, and I am using Wicked gem to give me multiform option on taking more data from the user and storing it in the user model.
Everything is working fine but know I'm trying to add another model where user has many degree. I am using cocoon gem to allow me to add extra degrees. I am able to go to the multiform page and enter degree information and even add many more degrees to the user, but when i submit the form i get an error;
param is missing or the value is empty: user
Everything else actually gets saved am i can view the user and the rest of the fields entered but non of the degrees.
user model:
has_many :degrees
accepts_nested_attributes_for :degrees, reject_if: :all_blank, allow_destroy: true
degree model:
belongs_to :user
user_steps_controller (this is the wicked gem controller):
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :personal, :avatar_and_about_yourself, :social, :education
def show
#user = current_user
render_wizard
end
def update
#user = current_user
#user.update_attributes(user_params)
render_wizard #user
end
private
def user_params
params.require(:user).permit(:name, :middlename, :lastname, :avatar, :aboutMe, :twitterlink, :githublink, :stackoverflowlink, :mediumlink, :dribblerlink, degrees_attributes: [:id, :degreeName, :university, :level, :done, :_destroy])
end
end
Registration controller (for devise):
class Users::RegistrationsController < Devise::RegistrationsController
# before_filter :configure_sign_up_params, only: [:create]
# before_filter :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
super
end
# POST /resource
def create
super
end
# PUT /resource
def update
super
end
protected
# The path used after sign up.
def after_sign_up_path_for(resource)
user_steps_path
end
# The path used after sign up for inactive accounts.
def after_inactive_sign_up_path_for(resource)
user_steps_path
end
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, :name, :middlename, :lastname, :avatar, :aboutMe, :twitterlink, :githublink, :stackoverflowlink, :mediumlink, :dribblerlink, degrees_attributes: [:id, :degreeName, :university, :level, :done, :_destroy])
end
end
Im getting an error in the user_steps_controller:
ActionController::ParameterMissing in UserStepsController#update
param is missing or the value is empty: user
and that the error is within this line:
def user_params
params.require(:user).permit(:name, :middlename, :lastname, :avatar, :aboutMe, :twitterlink, :githublink, :stackoverflowlink, :mediumlink, :dribblerlink, degrees_attributes: [:id, :degreeName, :university, :level, :done, :_destroy])
end
Also how would i go and view the entered fields, for example if i wanted to view user name it is:
<%= #user.name %>
but how would i show each degree? is it just a loop?
<% #users.degree.each do |degree| %>
Terminal log:
Started PATCH "/user_steps/education" for ::1 at 2016-02-09 11:23:29 +0000
Processing by UserStepsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"EXN7ts6xPVK8vkC7q6UcNleJJWLUtKmOw41T0qsDqXCrPJ0vIHrB/6xIfpp/o+cXKR47F+6LxUY5LjXlCobKZQ==", "commit"=>"Update User", "id"=>"education"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 16]]
Completed 400 Bad Request in 2ms (ActiveRecord: 0.2ms)
ActionController::ParameterMissing (param is missing or the value is empty: user):
app/controllers/user_steps_controller.rb:20:in `user_params'
app/controllers/user_steps_controller.rb:13:in `update'
This is too long to comment, I fully expect to update it.
The problem you have is that your form submission does not include any user params:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"EXN7ts6xPVK8vkC7q6UcNleJJWLUtKmOw41T0qsDqXCrPJ0vIHrB/6xIfpp/o+cXKR47F+6LxUY5LjXlCobKZQ==", "commit"=>"Update User", "id"=>"education"}
This might be Wicked, but judging form your spurious use of RegistrationsController, I'd imagine it could be to do with formatting etc:
--
If you're updating your current_user, you should not be invoking your registrations controller. That is for registrations -- you should be using your users controller:
#config/routes.rb
resource :user_steps, path: "user", only: [:show, :update] #-> url.com/user (singular resource)
#app/controllers/user_steps_controller.rb
class UserStepsController < ApplicationController
def show
end
def update
#user = current_user.update user_params
render_wizard #user
end
private
def user_params
params.require(:user).permit(:name, :middlename, :lastname, :avatar, :aboutMe, :twitterlink, :githublink, :stackoverflowlink, :mediumlink, :dribblerlink, degrees_attributes: [:id, :degreeName, :university, :level, :done, :_destroy])
end
end
This is just the controller; you need to look at how the form is being rendered & passed.
If you're sending data with the above code, you'll want to pull your form HTML and show how Wicked is working with it.
You need to be looking in app/views/users/show.html.erb for the form to display, not /app/views/registrations

Rails4 Entering data from one controller to multiple models

I have someone of a unique problem. I have 3 tables in the database that I need to populate with data. All tables are in relation to each other. The first table's info will be static and populated from a hash. The second table is the table that is usually targeted with data.
I am having a tough time trying to add data into the second table using strong parameters. I get an error param is missing or the value is empty: entries
Modles:
client.rb
class Client < ActiveRecord::Base
has_many :entries
end
Entry.rb
class Entry < ActiveRecord::Base
belongs_to :client_name
has_many :extra_data
end
extra_data.rb
class ExtraData < ActiveRecord::Base
belongs_to :entries
end
class ClientsController < ApplicationController
before_action :set_client, only: [:show, :update, :destroy, :edit]
# submit for all intended purposes.
#
def new
#entries = Entry.new()
end
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
redirect_to action: :index
else
flash.alert "you failed at life for today."
redirect_to action: :index
end
end
.
.
.
private
def submit_params
params.require(:entries).permit( :full_name,:email,:opt_in )
end
def set_client
#client = Client.find(params[:id])
end
end
form
<%= simple_form_for(:client, url: {:controller => 'clients', :action => 'create'}) do |f| %>
<%= f.input :full_name %>
<%= f.input :email %>
<%= f.input :opt_in %>
<%= f.button :submit, class: "btn-primary" %>
<% end %>
Routes:
Rails.application.routes.draw do
resources :clients do
resources :entries do
resources :extra_data
end
end
root 'clients#index'
end
In the Database Client data goes in with out a problem. I am having a problem getting the data from the form itself.
This answer is the culmination of a few different parts.
I figured out I was not saving any data into the model. So I needed to make another if statement.
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
if #entries.save
flash[:alert] = "Failure! everything is working."
redirect_to action: :index
else
flash[:alert] = "Success! at failing."
end
else
flash[:alert] = "you failed at life for today."
redirect_to action: :thanks
end
end
Also changing the form from :entries Helped. I also had a typo in my permit statment. I had :opt_in when I needed to use :optin Thanks #tmc

Confused in regards to nesting my community and comment

I will have an option were comment model can be used for a user to post in a person profile page and community page. Currently I'm working on community and would like some direction as I'm confused.
Current error I get is ActiveModel::ForbiddenAttributesError for my CommentsController#Create. Would like help if possible or help me point in the direction of fixing my mistake.
questions in regards to what view people are seeing comment is /communities/show
Models
User
has_one :profile
has_many :communities
has_many :comments, dependent: :destroy
Community
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
has_many :comments, dependent: :destroy
belongs_to :user
Comment
belongs_to :user
belongs_to :community
Routes
resources :communities do
resources :comments
end
Controllers
Communities
def show
#community = Community.friendly.find(params[:id])
#current_user = User.find(session[:user_id])
#comment = Comment.new
end
Comments
before_filter :load_community
def create
#comment = #community.comments.build(params[:comment])
#comment.user_id = current_user.id
if #comment.save
redirect_to :back
else
redirect_to "/"
end
# #comment = Comment.new(comment_params)
# #comment.user_id = session[:user_id]
# if #comment.save && #comment.community_id
# flash[:notice] = "Comment has been posted"
# else
# flash[:alert] = #comment.errors.full_messages
# end
end
private
def load_community
#community = Community.friendly.find(params[:community_id])
end
def comment_params
params.require(:comment).permit(:text, :user_id, :community_id, :profile_id)
end
Views
/communities/show
<%= render "profiles/index" %>
<h4><%= #community.title.capitalize! %></h4>
<%= #community.bio %>
<%= render "comments/new" %>
/comments/_new
<%= form_for ([#community, #comment]) do |f| %>
<%= f.text_area :text, placeholder: "Enter New Comment Here ...", :cols => 50, :rows => 3, :class => 'text_field_message', :id => 'new_comment' %>
<%= f.submit :class => 'new_comment_button' %>
<% end %>
Thank you everyone who helps explain where I'm making my mistake and also sorry in advance if I may need to ask what you might be requesting from me. For further questions please ask.
UPDATE
What I see in my console is
Started POST "/communities/dang/comments" for 127.0.0.1 at 2015-10-23 18:38:47 -0400
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"us8KNTLUZUdao13GK4OQId0YoUqf+CeLFIGjydnyWtI=", "comment"=> {"text"=>"www"}, "commit"=>"Create Comment", "community_id"=>"dang"}
Community Load (0.1ms) SELECT "communities".* FROM "communities" WHERE "communities"."slug" = 'dang' ORDER BY "communities"."id" ASC LIMIT 1
Completed 500 Internal Server Error in 11ms
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/comments_controller.rb:17:in `create'
Okay.
ActiveModel::ForbiddenAttributesError for my CommentsController#Create
This basically means you're not permitting the required attributes in your create method.
This is what you need:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = #community.comments.new comment_params
#comment.save
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id)
end
end
You should read up on strong params to better understand how this works.
Polymorphic
You also have another issue which can be solved with a polymorphic association:
Simply, this allows you to associate a model with any number of others.
In your instance, where you can comment on users and communities, this functionality will serve well:
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :sent_comments, class_name: "Comment", foreign_key: :user_id
has_many :comments, as: :commentable
end
#app/models/community.rb
class Community < ActiveRecord::Base
has_many :comments, as: :commentable
end
This will allow you to perform the following:
#user = User.find params[:id]
#user.comments.new comment_params
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id) #-> current_user from Devise
end
This allows you to use #user.comments and #community.comments with a single association.
You'll have to migrate the commentable_id & commentable_type columns into your comments table, but after that the above code should work.

Finding and using the id of a permalink page for non logged in users with ruby on rails

I'm modifying a micro post ruby on rails tutorial app and am running into the following problem that has me stumped:
A user has a permalink url, http://localhost:3000/users/exampleuser. Visitors can come to this url and answer a survey poll. The following code works if exampleuser (current_user) is logged in to their own account. However if you come as a Visitor, who is not required to log in, I receive what looks like a Null User ID error. All my attempts at trying to assign the correct user id have been unsuccessful even though it looks to me that the user id is not null anymore.
Here is the error I receive:
Started POST "/polls" for 127.0.0.1 at 2012-02-24 20:28:56 -0500
Processing by PollsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZE/KXWCnBfE8PAn1CyAM51rnQI6z2Ut1UvHavEqSkZY=", "poll"=>{"overall_grade"=>"strong", "relevance"=>"strong", "personalization"=>"strong", "design"=>"strong", "value_proposition"=>"strong", "responder_name"=>"test", "responder_email"=>"test#test.com", "comments"=>"test"}, "commit"=>"Submit Grade"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
Completed 500 Internal Server Error in 47ms
RuntimeError (Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id):
app/controllers/polls_controller.rb:5:in `create'
Using the console I can see that the first user (exampleuser) is ok.
ruby-1.9.2-p290 :001 > User.find(1)
User Load (13.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1
[["id", 1]] => #<User id: 1, name: "Example User", email: "example#railstutorial.org",
created_at: "2012-02-23 17:27:45", updated_at: "2012-02-23 17:27:45",
encrypted_password: "418b54481fffe05051621c500d69e44fd25573145c0b12e1860...", salt:
"57d9f6da0f6554e92c4180a469d5a1807c4a9dd46ce47c30b45...", admin: true, username:
"exampleuser", permalink: "exampleuser">
But this logic doesn't work in my Polls controller for some reason. Specifically I believe that the following lines in Polls Controller are the issue:
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
Any insights would be most appreciated.
John
Polls Controller
class PollsController < ApplicationController
def create
if current_user.blank?
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
if #poll.save
flash[:success] = "Pitch graded successfully!"
redirect_to root_path
else
render 'pages/home'
end
end
end
Polls Model
class Poll < ActiveRecord::Base
attr_accessible :overall_grade, :personalization, :relevance, :value_proposition, :design, :other, :responder_name, :responder_email, :comments, :next_steps
belongs_to :user
validates :user_id, :presence => true
validates :overall_grade, :presence => true
default_scope :order => 'polls.created_at DESC'
end
Users Model
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :username, :email, :password, :password_confirmation
has_many :polls, :dependent => :destroy
username_regex = /\A[\w\-]+\z/i
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :username, :presence => true,
:length => { :maximum => 50 },
:format => { :with => username_regex },
:uniqueness => { :case_sensitive => false }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password, :create_permalink
#Returns true if the user's password matches the submitted password
def has_password?(submitted_password)
#Compare stored to submitted encrypted versions
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
#handles 2 scenarios: invalid email and a successful email, password mismatch implicitly since returns nil at end of method
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
def to_param
permalink
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
def create_permalink
self.permalink = username.downcase
end
end
Users Controller
class UsersController < ApplicationController
before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
before_filter :correct_user, :only => [:edit, :update]
before_filter :admin_user, :only => [:index, :destroy]
def show
#user = User.find_by_permalink(params[:id])
#polls = #user.polls.paginate(:page => params[:page])
#title = #user.name
#poll = Poll.new
end
def new
#user = User.new
#title = "Sign up"
end
def create
#user = User.new(params[:user])
if #user.save
#Handle a successful save.
sign_in #user
flash[:success] = "Signup Success welcome to Grademypitch!"
redirect_to #user
else
#title = "Sign up"
#user.password = ""
render 'new'
end
end
def edit
#title = "Edit user"
end
def update
#user = User.find_by_permalink(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
def index
#title = "All users"
#users = User.paginate(:page => params[:page])
end
def destroy
User.find_by_permalink(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_path
end
private
def authenticate
deny_access unless signed_in?
end
def correct_user
#user = User.find_by_permalink(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
Pages Controller
class PagesController < ApplicationController
def home
#title = "Home"
#poll = Poll.new
end
def contact
#title = "Contact"
end
def about
#title = "About"
end
def help
#title = "Help"
end
end
Routes
SampleApp::Application.routes.draw do
# removed when added session new/create/destroy.... get "sessions/new"
#get "users/new" , can remove now that resources :users added cause it automatically adds all routes for users!
resources :users
resources :sessions, :only => [:new, :create, :destroy]
resources :polls, :only => [:new, :create]
match '/signup', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
root :to => 'pages#home'
match '/contact', :to => 'pages#contact'
match '/about', :to => 'pages#about'
match '/help', :to => 'pages#help'
end
Yes, line 5 of your PollsController is the problem:
user_to_grade = User.find_by_id(#user.id)
And it doesn't look like you've defined #user anywhere in your PollsController.
Here is the relevant part:
if current_user.blank?
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
When the user is logged in, you're building a poll on the current user. When the user isn't logged in, you're first trying to find a user with the same id as #user (nil) which fails, and then trying to build a poll on that user.
What exactly is your desired behavior for users that aren't logged in? Should they be able to hit the create action at all?
So I figured this out.
Step 1 - I installed ruby-debug which helped me determine that the #user.id was nil when it reached the polls controller. So between the users controller and the polls controller the #user.id was being forgotten.
Step 2 - I researched that the session function as a possible solution so I modified the following areas to:
Users Controller
def show
#user = User.find_by_permalink(params[:id])
#polls = #user.polls.paginate(:page => params[:page])
#title = #user.name
#poll = Poll.new
if current_user.blank?
session[:remember_token] = #user.id
end
end
Here I added a session token to remember the user id from the user page with the permalink - i.e. users/exampleuser. So when I now goto the Polls controller it can call upon the session token and find out what user id we were coming from with the following code...
Polls Controller
def create
if current_user.blank?
user_to_grade = User.find_by_id(session[:remember_token])
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
.
.
.
The key line is the User.find_by_id(session[:remember_token]). This now uses the user id from the user controller and is not nil anymore which solves the issue of how to remember the user id from controller to controller!

Resources