Creating homes using nested routes - ruby-on-rails

First this is all of my code
#models/user.rb
class User < ApplicationRecord
has_many :trips
has_many :homes, through: :trips
has_secure_password
accepts_nested_attributes_for :trips
accepts_nested_attributes_for :homes
validates :name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :password, presence: true
validates :password, confirmation: { case_sensitive: true }
end
#home.rb
class Home < ApplicationRecord
has_many :trips
has_many :users, through: :trips
validates :address, presence: true
end
class HomesController < ApplicationController
def show
#home = Home.find(params[:id])
end
def new
if params[:user_id]
#user = User.find_by(id: params[:user_id])
#home = #user.homes.build
end
end
def create
#user = User.find_by(id: params[:user_id])
binding.pry
#home = Home.new
end
private
def home_params
params.require(:home).permit(:address, :user_id)
end
end
I am trying to do something like this so that the home created is associated with the user that is creating it.
def create
#user = User.find_by(id: params[:user_id])
#home = Home.new(home_params)
if #home.save
#user.homes << #home
else
render :new
end
end
The problem is that the :user_id is not being passed into the params. So the #user comes out as nil. I can't find the reason why. Does this example make sense? Am I trying to set the associations correctly? Help or any insight would really be appreciated. Thanks in advance.

The way you would typically create resources as the current user is with an authentication such as Devise - not by nesting the resource. Instead you get the current user in the controller through the authentication system and build the resource off it:
resources :homes
class HomesController < ApplicationController
...
# GET /homes/new
def new
#home = current_user.homes.new
end
# POST /homes
def create
#home = current_user.homes.new(home_parameters)
if #home.save
redirect_to #home
else
render :new
end
end
...
end
This sets the user_id on the model (the Trip join model in this case) from the session or something like an access token when dealing with API's.
The reason you don't want to nest the resource when you're creating them as a specific user is that its trivial to pass another users id to create resources as another user. A session cookie is encrypted and thus much harder to tamper with and the same goes for authentication tokens.
by using if params[:user_id] and User.find_by(id: params[:user_id]) you are really just giving yourself potential nil errors and shooting yourself in the foot. If an action requires a user to be logged use a before_action callback to ensure they are authenticated and raise an error and bail (redirect the user to the sign in). Thats how authentication gems like Devise, Knock and Sorcery handle it.

Related

Rails 6 validate model only for specific controller

I've got User model with validation:
validates :experience_level, inclusion: { in: EXPERIENCE_LEVEL, allow_blank: true }
But one of the part of full registration is to update User's experience level. User can do this by inside of below controller:
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params)
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
And for that endpoint I want to use
validates :experience_level, presence: true, inclusion: { in: EXPERIENCE_LEVEL }
I know I could use on: :update but in such case User will not be able to update e.g. password if it doesn't go through the experience update form first.
If you want to make the model state aware you can do it by explicitly passing information into the model:
class User < ApplicationRecord
attr_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
stage == :add_experience_level
end
end
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params.merge(stage: :add_experience_level))
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
end
There is also ActiveSupport::CurrentAttributes:
Abstract super class that provides a thread-isolated attributes
singleton, which resets automatically before and after each request.
This allows you to keep all the per-request attributes easily
available to the whole system.
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :stage
end
def update
authorize current_user
Current.stage = :add_experience_level
end
class User < ApplicationRecord
attribute_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
Current.stage == :add_experience_level
end
end
Its really up to you if you want use it though as it can be considered harmful. If it quacks like a global...

Can Access data of associated model in console but not in app

In my app i have two types of users: students and companies.
During the registration process, i manage with the wicked gem, the user is asked to fill in data depending on the accounttype (student or company) he chose.
For example, a student have to fill in his university, a company have to fill in the amount of employers.
When the registration process is finished, everything is fine, but somehow i cant access the data of the user which chose accounttype 2 - company.
The data is stored in two seperated associated models:
For students: accountinfo (ye i know its against convention and should have the name account_info)
For companies: accountinfos_company (ye i know, weird pluralization .. but it does the trick and i am not confused by it)
Everything works fine for the students. I am able to fill in data and the data is properly saved within a nested form. Same goes for the companies but unfortunately i am not able to output the data of accountinfos_company in my app. What i can do is for example:
#user.accountinfo.description
For student-accounts. But if I try:
#user.accountinfos_company.description
The output fails with an error
undefined method `accountinfos_company' for #<User::ActiveRecord_Relation:0x007fc1851e6e20>
SOMEHOW I am able to output the data of the particular user via console like: User.last.accountinfos_company.description. I copied every step I made for the first associated model, the data gets properly saved but I am not able to access it inside the app.
I thought it might be something missing, some definition or sth like this but as far as i can see, everything is fine.
users_controller.rb (User association stuff gets created in another controller)
class UsersController < ApplicationController
before_action :authenticate_user!
def index
redirect_to root_path
end
def create
#user = User.create( user_params )
end
def show
#user = User.find(params[:id])
if #user.not_complete?
redirect_to user_steps_path
else
render 'show'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
# might be the right way?
#user.update(user_params)
if #user.update(user_params)
redirect_to User.find(params[:id]), notice: 'Profil bearbeitet.'
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :accounttype,
:accountinfo, :accountinfos_company, :profile_image, :active, accountinfo_attributes:[:id], accountinfos_company_attributes:[:id])
end
end
user_steps_controller.rb
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :welcome, :info
before_action :authenticate_user!
def show
#user = current_user
render_wizard
end
def create
#user = current_user
if #user.accounttype == 1
#accountinfo = #user.accountinfo.create(user_params)
elsif #user.accounttype == 2
#accountinfos_company = #user.accountinfos_company.create(user_params)
end
end
def update
#user = current_user
#user.update(user_params)
render_wizard #user
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :accountinfo, :accountinfos_company,:profile_image, :active, accountinfo_attributes:[:id, :city, :competence, :description, :university], accountinfos_company_attributes:[:id, :city, :company_type, :description, :company_name, :employer_amount])
end
private
def redirect_to_finish_wizard_path
redirect_to root_path, notice: "Danke für deine Zeit!"
end
end
user.rb
class User < ApplicationRecord
has_one :accountinfo
has_one :accountinfos_company
has_many :offer_posts
has_many :search_posts
accepts_nested_attributes_for :accountinfo, update_only: true
accepts_nested_attributes_for :accountinfos_company, update_only: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :first_name, :last_name, :email, :accounttype
end
accountinfo.rb and accountinfos_company.rb
belongs_to :user
I dont know if i have something missed but yeah.
If you could help me i would be really glad.

Issue with uploading using CarrierWave_Direct

I'm pretty new to ruby and rails and I'm having some problems uploading using carrierwave_direct. I followed Ryan Bates' Railscast 383 on uploading using carrierwave_direct. However, the solution he provides doesn't seem to be working for me. I have a User model that I created using Devise and I have an uploader that handles my uploading. I am trying to use the uploader to upload video files which I plan to later transcode using the Elastic Transoder with the AWS API. The files are uploading fine to s3, but it appears that they are not getting associated with a record in my database.The files upload as soon as I submit them, but I need a way to create them along with a title, a description, and potentially other parameters later. My if-else statement always redirects to my else statement as well. I think my problem is in my controller; my routes and views seem to be working fine, I'm just stuck on this one issue.
To clarify further: I need users to be able to upload a video and then have the user input a title, description, etc. and then be redirected to another page. I then need to be able to show that file later when called upon.
Here is my UploadsController:
class UploadsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
before_action :current_user, only: :destroy
def index
#uploader = Upload.new.video
#uploader.success_action_redirect = new_uploads_url
end
def show
#upload = Upload.find(params[:id])
end
def new
#upload = Upload.new(key: params[:key])
end
def create
#upload = Upload.new(upload_params)
if #upload.save
redirect_to home_index_path
return
else
redirect_to uploads_index_path
end
end
def edit
end
def destroy
#upload = Upload.find(params[:id])
#upload.destroy
flash[:notice] = "Upload deleted"
redirect_to request.referrer || root_url
end
def upload_params
params.require(:upload).permit(:video, :title, :description)
end
end
Here is my Upload Model:
class Upload < ActiveRecord::Base
belongs_to :user
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :description, presence: true
validates :title, presence: true, length: {maximum: 100}
validates :description, presence: true
validates :video, presence: true
mount_uploader :video, VideoUploader
after_save :enqueue_video
def enqueue_video
VideoWorker.perform_async(id, key) if key.present?
end
class VideoWorker
include Sidekiq::Worker
def perform(id, key)
upload = Upload.find(id)
upload.key = key
video.remote_video_url = upload.video.direct_fog_url(with_path: true)
upload.save!
end
end
end
And my routes:
Rails.application.routes.draw do
get '/home/index'
root 'home#index'
devise_for :users
get 'users/:id' => 'users#show'
resources :uploads
get '/index' => 'uploads#index'
end
EDIT Here is my UsersController and User Show Page as well:
Controller:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
Show:
<h1><%= #user.username %></h1>
<h3>Title:</h3>
<%= #upload.title %>
The problem is that you're doing:
def create
#upload = Upload.new(upload_params)
# [..]
end
def upload_params
params.require(:upload).permit(:video, :title, :description)
end
And that your Upload is linked to a User with:
class Upload < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
# [..]
end
So how does Rails know which Upload belongs to which User? You never told Upload anything 'bout no User!
If you always want to use the currently logged in user, you could probably do something like:
def create
#upload = Upload.new(upload_params)
#upload.user = current_user
# [...]
end
This assumes, of course, that current_user is the correct method to get the currently logged in user (it usually is).
If you want to be able to connect it to any other user, you need to add a user_id field in the and add it to upload_params.

Newsfeed in Rails, unidentified method

I'm creating a simple newsfeed in rails. The aim is for it to return all the posts from the groups the user is following. I am using socialization for my follow functionality.
The exact error is:
NoMethodError (undefined method `followees' for false:FalseClass)
Here are my basic models not including like and follow as they're empty:
User:
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :username, :password, :email
has_many :groups
has_many :posts
acts_as_follower
acts_as_liker
before_create :generate_auth_token
def auth_token_expired?
auth_token_expires_at < Time.now
end
def generate_auth_token(expires = nil)
self.auth_token = SecureRandom.hex(20)
self.auth_token_expires_at = expires || 1.day.from_now
end
def regenerate_auth_token!(expires = nil)
Rails.logger.info "Regenerating user auth_token"
Rails.logger.info " Expiration: #{expires}" if expires
generate_auth_token(expires)
save!
end
end
Group:
class Group < ActiveRecord::Base
attr_accessible :description, :name, :user_id
has_many :posts
belongs_to :user
acts_as_followable
end
Post:
class Post < ActiveRecord::Base
attr_accessible :body, :user_id, :group_id
belongs_to :user
belongs_to :group
acts_as_likeable
end
I have setup a function named newsfeed in my post controller. The function grabs all the groups that a user is following and then grabs all the posts that have group_ids matching group_ids in the returned groups array. But I keep getting unidentified method followees(socialization provides this). Yet it appears to work when using single users and posts in irb.
def newsfeed
#groups = current_user.followees(Group)
#posts = Post.where(:group_id => #groups)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #posts }
end
end
Thanks for any help.
Apparently, your current_user method returns false, instead of a user. Check what's returned from that method, as find out why you get the error...
Your current_user return false instead of instance of User. You may see it from error text.

How to create another object when creating a Devise User from their registration form in Rails?

There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User is created along with this Designer object with its user_id set to the new User that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User and Designer upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:signup])
if #signup.save
sign_in #signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless #user.valid?
delegate_errors_for_account unless #account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
#user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
#account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, #user.errors[:name].first) if #user.errors[:name].present?
errors.add(:email, #user.errors[:email].first) if #user.errors[:email].present?
errors.add(:password, #user.errors[:password].first) if #user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, #account.errors[:name].first) if #account.errors[:name].present?
end
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end

Resources