NoMethodError (undefined method `posts' for nil:NilClass): - ruby-on-rails

Somebody help me pleases, I sent local server post method but happened as the above error so i tried a lot of things recreate table, changed path and model name and so on
However I couldn't slove it , I want to know how to fix it and why happed this error,
Ruby 2.5.3 Rails 6.0.0 rc2
Controller
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new]
def index
#posts = current_user.posts.all
end
def new
#post = Post.new
end
# Maybe here is happening error
def create
if #post = current_user.posts.build(posts_params)
flash[:success] = "You created post"
redirect_to #posts
else
flash[:failed] = "You failed posted "
end
end
def edit
end
def show
end
private
def posts_params
params.require(:posts).permit(:title , :content , :user_id)
end
end
Routes
Rails.application.routes.draw do
devise_for :users , :paths => 'users'
resources :users do
resources :posts , :except => :edit
end
root 'users#index'
end
User model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:authentication_keys => [:name]
validates :name, presence: true, uniqueness: true , length:{maximum: 10}
has_many :posts
Thanks everyone, It seems to be problem is current_user
So, I use byebug so that (byebug) current_user
User Load (0.5ms) SELECT users.* FROM users WHERE users.id = 1 ORDER BY users.id ASC LIMIT 1
↳ (byebug):1:in `create'
Again thanks everyone, I rewrote code
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:index, :new, :create]
def index
#posts = current_user.posts.all
end
def new
#post = Post.new
end
def create
if #post = current_user.posts.create(posts_params)
flash[:success] = "You created post"
redirect_to user_posts_path
else
flash[:failed] = "You failed posted "
end
end
def edit
end
def show
end
private
def posts_params
params.require(:posts).permit(:title , :content , :user_id)
end
end
Sadly this code also doesn't work , just in case , i wrote form code
<%= form_with model: #post , url: user_posts_path ,local: true do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :content %>
<%= f.text_area :content %>
<% f.label :user_id %>
<% f.hidden_field :user_id %>
<%= f.submit "Create Post" %>

In your Controller, you only require user to login for action new. This causes every other page user can visit / interact without authentication.
So, with your controller code, you will get the error undefined posts for nil if there's no logged in user with this call current_user.posts
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new]
def index
#posts = current_user.posts.all
end
def new
end
def create
if #post = current_user.posts.build(posts_params)
# ...
end
end
def edit
end
def show
end
end
To fix this error, you can add more actions requiring user authentication
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:index, :new, :create]
end
or just require it for all actions of the controller
class PostsController < ApplicationController
before_action :authenticate_user!
end

There are several errors in the code:
Check user for create action
before_action :authenticate_user!, only: %i[new create]
Use create method instead of build method and fix redirect problem.
def create
if #post = current_user.posts.create(posts_params)
flash[:success] = "You created post"
redirect_to #post
else
flash[:failed] = "You failed posted"
end
end
However, if you get an error, check the devise configuration for current_user.

Change from
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new]
end
to
class PostsController < ApplicationController
before_action :authenticate_user!
end
Because you call current_user in index action, and basically I think all the actions requires user to log in.

Related

Rails: How can I change Boolean values in a model?

I have a model called "Clients". The Clients model belongs to the Users model (Users model is associated with devise). The Client can do payments to me manually (cash only). When the client does this payment, I give them access to more pages in the website. To do this, I was thinking I add a migration to the Client model like:
rails generate migration add_paid_to_clients paid:boolean
Then when the clients pay the money, I can just go to their profile and click on an option (maybe a checkbox) saying the client has paid. Only the admin(me) can see this part. The way I would implement that is, in the user profile view:
<% if current_user.user_type == :client%>
userinformation....blablabla
<% elsif current_user.user_type == :admin %>
userinformation....blablabla
AND the checkbox I talked about.
<% end %>
The user_type is a predefined function to figure out if the current_user is admin or just a client. I have done this part.
So in the private pages(the pages you can get access to after paying), I can have some logic like If current_user.paid? THEN show them this page.
How can I implement the checkbox part? After the migration(if there is a better way,please let me know),how can I just "flip the switch" to let them get more access?
My Clients contoller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
def find_client
#client = Client.find(params[:id])
end
end
This is not real cash, this is a project for school.
Something like this?
In the form for the admin:
<%= form_for #user do |f| %>
<%= check_box_tag 'paid', 'yes', true %>
<%= f.submit "Update" %>
<% end %>
Then in the controller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
if current_user.user_type == :admin
# This is different because only admin can update paid
# To make a string a boolean
params[:client][:paid] = params[:client][:paid] == 'yes'
params.require(:client).permit(:paid, :name, :company, :position, :number, :email, :client_img)
else
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
end
def find_client
#client = Client.find(params[:id])
end
end
you can do this more properly using the protected attributes, described here
After that you want to make a PaidController
class PaidController < ApplicationController
before_filter :authorize_paid
def authorize_paid
return head(:forbidden) unless current_user.paid?
end
end
Then make sure that all the protected pages inherit from the PaidController

Authorization settings using pundit gem rails

I'm new at rails so bear with me pls. My problem is so specific. I'm creating a User blog, where they could put any posts. So Users has a blogs, and blogs has posts. So when user create a blog, all posts in his blog should be written by him. Other users can't write not on their blogs.
post_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, only: [:edit, :update, :destroy]
expose :blog
expose :post
def show
end
def new
end
def edit
end
def create
post.user = current_user
post.save
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def update
post.update(post_params)
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def destroy
post.destroy
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
private
def authorize_user!
authorize(post, :authorized?)
end
def post_params
params.require(:post).permit(:title, :content, :user_id, :blog_id)
end
end
Here i'm using pundit to authorize user, when they update or destroy posts (users can update or destroy only their own posts) and it works perfectly.
views/posts/new
.row
.columns
h2 = title("New post")
.row
.medium-5.columns
= simple_form_for post do |f|
= f.error_notification
.form-inputs
= f.input :title
= f.input :content
= f.hidden_field :blog_id, value: blog.id
.form-actions
= f.button :submit
Here i'm using the hidden form to set the blog_id which I take from params. Http link looks like http://localhost:3000/posts/new?blog_id=6. The problem is that each user can copy this link to create the post( and they are not the blog owners).
post_policy.rb
class PostPolicy < ApplicationPolicy
def authorized?
record.user == user
end
end
How should I check the blog's owner before post creating? Maybe I have a wrong way to create posts like this(using hidden form).
Link to create new post
= link_to 'New Post', new_post_path(blog_id: blog.id)
I hope, it will work for you
application_controller.rb
class ApplicationController
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
before_action :authenticate_admin_user!
helper_method :current_user
def pundit_user
current_admin_user
end
def current_user
#current_user ||= User.find(current_admin_user.id)
end
end
posts_controller.rb
class PostsController < ApplicationController
before_action :set_blog
def new
authorize(Post)
end
def edit
#post = #blog.posts.find(params[:id])
authorize(#post)
end
def index
#posts = policy_scope(#blog.posts)
end
private
def set_blog
#blog = current_user.blogs.find(params[:blog_id])
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
def show?
true
end
def index?
true
end
def new?
create?
end
def create?
true
end
def edit?
update?
end
def update?
scope_include_object?
end
def destroy?
scope_include_object?
end
class Scope < Scope
def resolve
scope.joins(:blog).where(blogs: { admin_user_id: user.id })
end
end
def scope_include_object?
scope.where(id: record.id).exists?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :admin_users
resources :blogs do
resources :posts
end
end

Can't save form_for one controller in view partial, rails 4

This has been asked on SO a lot before, but I can't find anything that quite applies. What I'm trying to do is render an edit form for SettingsController in the edit view of UsersController. I'm super new to RoR, so I'm not even sure what I'm doing wrong.
This questions seems closest, but when I initialize #setting = Setting.new in the Users controller, I get a Settings form without the defaults set for new users in the migration. But if I initialize #setting = Setting.edit or Setting.update, I get an undefined method or wrong number of arguments error.
When the Setting.new form is saved, it throws this error:
undefined method for find_by_id in the SettingsController: app/controllers/settings_controller.rb:43:in `correct_user'.
When I check the database, the settings records are being correctly created when a user is created, but the settings record is not updated when the form is saved.
setting.rb:
class Setting < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
user.rb:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_one :setting, dependent: :destroy
after_create :create_setting
end
UsersController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update, :index, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
#setting = Setting.update
end
def update
#user = User.find(params[:id])
#setting = Setting.update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated!"
redirect_to #user
else
render 'edit'
end
end
def settings
#title = "Settings"
#setting = Setting.find_by_user_id(params[:user_id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
the SettingsController:
class SettingsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :show, :index]
before_action :correct_user, only: [:create, :edit, :update, :show, :index]
def index
#settings = Setting
end
def show
#setting = User.find(params[:id]).setting
end
def new
#setting = Setting.new
end
def edit
#setting = Setting.find(params[:id])
end
def create
#setting = current_user.settings.build(setting_params)
#setting.save
end
def update
#setting = Setting.find(params[:id])
if #setting.update_attributes(post_params)
flash[:success] = "Settings updated!"
redirect_to request.referrer
else
render 'edit'
end
end
private
def setting_params
params.require(:setting).permit(:reading_theme)
end
def correct_user
#setting = current_user.setting.find_by_id(params[:id]) ##the line which throws an error when the form is saved
redirect_to root_url if #setting.nil?
end
end
The form partial:
<%= form_for(#setting) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= radio_button_tag(:reading_theme, "flatly") %>
<%= label_tag(:reading_theme_flatly, "Light (Default)") %>
</div>
<div class="field">
<%= radio_button_tag(:reading_theme, "darkly") %>
<%= label_tag(:reading_theme_darkly, "Dark") %>
</div>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
routes.rb:
resources :users do
member do
get :following, :followers
end
end
resources :settings, only: [:new, :create, :edit, :update]
...
ETA: the settings migration:
class CreateSettings < ActiveRecord::Migration
def change
create_table :settings do |t|
t.string :reading_theme, default: => "flatly"
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
How do I get the proper defaults so that my form can be saved correctly?
Any defaults that you include for fields in the migration will be "unknown" to the model class (Setting) in Ruby. Ruby (or rather Rails ActiveRecord) does not read the default values from the table definition when creating a model object. This can lead to a dual personality problem like you're seeing here.
What you have to do is to add the relevant defaults into the Ruby code, where appropriate. For example, in your Settings controller, you can make these changes:
def new
#setting = Setting.new
# Set any defaults that will be visible to the user on the form
#setting.reading_theme = "flatly"
# The form will allow the user to choose their own values, based on the defaults
end
def create
#setting = current_user.settings.build(setting_params)
# Set any defaults that will NOT be visible to the user
#setting.save
end
This gives you the ability to distinguish between default values that are visible to the user and defaults that are not.
Note that you also have the option of establishing defaults when you create the model object, but this may be more complicated in some situations, and seems to be far less common in practical use. There's an SO answer for that in How to initialize an ActiveRecord with values in Rails?, in case this better suits your needs.
Not can use find_by_id in has_one relationship
#setting = current_user.setting.find_by_id(params[:id])
Just #setting = current_user.setting

Can only create one user profile - Rails 4.2.4

I'm using devise for my user auth and registration. I can register a user no problem. Im also using friendly. My issue is, I can only create one user profile.
The setup...
user.rb:
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
validates :name, uniqueness: true, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile # each user should have just one profile
end
profile.rb:
class Profile < ActiveRecord::Base
belongs_to :user
end
profiles_controller.rb:
class ProfilesController < ApplicationController
before_action :authenticate_user!
before_action :only_current_user
def new
# form where a user can fill out their OWN profile
#user = User.friendly.find( params[:user_id] )
#profile = Profile.new
end
def create
#user = User.friendly.find( params[:user_id] )
#profile = #user.build_profile(profile_params)
if #profile.save # Not saving!!
flash[:success] = 'Profile Created!'
redirect_to user_path( params[:user_id] )
else
render action: :new # keeps rendering!
end
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :avatar, :job_title, :phone_number, :business_name)
end
end
Why is it that only one user can create a profile and not others? Is it has to do with the relations?
We use this setup with some of our apps - User -> Profile.
In short, you should build the profile at User creation. Then you can edit the profile as you need. Your problem of having a Profile.new method is very inefficient...
#app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
before_create :build_profile #-> saves blank associated "Profile" object after user create
end
This will mean that each time a User is created, their corresponding Profile object is also appended to the db.
This will give you the capacity to edit the profile as required:
#config/routes.rb
resources :users, path_names: { edit: "profile", update: "profile" }, only: [:show, :edit, :update]
This will give you the opportunity to use the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!, only: [:edit, :update]
before_action :authorize, only: [:edit, :update]
def show
#user = User.find params[:id]
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update user_params
end
private
def authorize
id = params[:id]
redirect_to user_show_path(id) if current_user.id != id #-> authorization
end
def user_params
params.require(:user).permit(:x, :y, :z, profile_attributes: [:homepage, :other, :profile, :attributes])
end
end
The view/form would be the following:
#app/views/users/edit.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :profile do |f| %>
<%= f.text_field :homepage %>
...
<% end %>
<%= f.submit %>
<% end %>
In regards your current setup:
def new
#profile = current_user.profile.new
end
def create
#profile = current_user.profile.new profile_params
if #profile.save
redirect_to user_path(params[:id]), notice: "Profile Created!"
else
render action: :new
end
end
private
def profile_params
params.require(:profile).permit(:x, :y, :z)
end
Not sure why you don't create the profile in the after_create event of the user. As soon the user is created - create an empty (but associated) profile.
class User
has_one :profile, dependent: :destroy
after_create {
build_profile unless profile
profile.save
}
end
class Profile
belongs_to :user, autosave: true
end
so then, in your controller you just need the update method.
def update
if current_user.profile.update_attributes(user_params)
flash_it :success
return redirect_to edit_user_profile_path
else
flash_it :error
render :edit
end
end

saving user id in rails user-submissions-contest has many through model

How to configure the rails controller so I can have a user post a submission in no matter what contest. When they post their user id and the contest id should be automatically appended to the submission.
I know I can do:
User.first.contests.create => let the user create a contest
Contest.first.submissions.create => create a submission in a contest (not linked to a user)
User.first.submissions.create => create a submission linked to a user but not to a contest
I cannot do User.first.Contest.last.submissions.create => I want to link a submission to a contest and to a submission.
Is there an elegant way to fix this?
The submission controller looks like this:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
# the current user can only edit, update or destroy if the id of the pin matches the id the user is linked with.
before_action :correct_user, only: [:edit, :update, :destroy]
# the user has to authenticate for every action except index and show.
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#title = t('submissions.index.title')
#submissions = Submission.all
respond_with(#submissions)
end
def show
#title = t('submissions.show.title')
respond_with(#submission)
end
def new
#title = t('submissions.new.title')
#submission = Submission.new
respond_with(#submission)
end
def edit
#title = t('submissions.edit.title')
end
def create
#title = t('submissions.create.title')
#submission = Submission.new(submission_params)
#submission.save
respond_with(#submission)
end
def update
#title = t('submissions.update.title')
#submission.update(submission_params)
respond_with(#submission)
end
def destroy
#title = t('submissions.destroy.title')
#submission.destroy
respond_with(#submission)
end
private
def set_submission
#submission = Submission.find(params[:id])
end
def submission_params
arams.require(:submission).permit(:reps, :weight, :user_id)
end
def correct_user
#submission = current_user.submissions.find_by(id: params[:id])
redirect_to submissions_path, notice: t('submissions.controller.correct_user') if #submission.nil?
end
end
I have following models:
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
class Submission < ActiveRecord::Base
belongs_to :user
belongs_to :contest
class User < ActiveRecord::Base
has_many :submissions
has_many :contests, through: :submissions
I think you're making this a bit complicated.
Submission is POSTED within Contest, Submission needs to know the user_id.
<%= simple_form_for :submission, url: contest_submissions_path(contest) do |f| %>
...
<%= f.submit 'Submit', class: "button" %>
<% end %>
And on your submissions CREATE method
class SubmissionsController < ApplicationController
def create
#contest = Contest.find(params[:contest_id])
#submission = #contest.submissions.new(submission_params)
#submissions.user = current_user
.....
end
The magic happens at #submissions.user = current_user If you are using Devise, it is easy to pass in the current_user.id ANYWHERE in the controller, as I just did in the submissions controller.
Are you able to use #submission = current_user.submissions.new(submission_params) and #contest = Contest.find(params[:contest_id]) in your SubmissionsController#create
EDIT: I've added some details on adding a reference to contest_id in the submissions table.
The best way I've found to tie related things together in Rails (and indeed, any relational database) is to add a reference in the child table to the parent's id. You can do this with a migration in Rails.
rails g migration AddContestToSubmission contest:references
And modify the migration file generated in your db/migrate/<datetime>_add_contest_to_submission to look similar to:
class AddContestToSubmission < ActiveRecord::Migration
def change
add_reference :submissions, :contest, index: true
end
end
Then go ahead and look at your submissions table in your schema.rb. You should notice something like t.integer "contest_id" You should probably also add the user_id in your migration is you want a submission to be tied to one user.

Resources