When creating child, also create parent - ruby-on-rails

In my project I have two models: Responder (which is basically a message) & Conversation. Responder is a child of Conversation. What I want is that if a user creates a new responder, it automatically creates a conversation for this Responder. The Conversation only has an id.
if the admin then replies on this Responder, the conversation_id for this responder will be the same as the user's responder.
So what I want:
User/admin creates responder (child), also create the conversation (parent)
Admin/user uses reply button, conversation.id of responder replied on is passed on to the new responder.
I've looked around a bit, and found some similar questions. But they aren't quite what I want, and I can't figure out how to build around it so it works for me. I feel it's a very simple thing to do, but I really have no idea how and where to start.
Conversation.rb:
class Conversation < ActiveRecord::Base
has_many :responders
end
Responder.rb:
class Responder < ActiveRecord::Base
validates :title, :text, presence: true
has_many :receivers
belongs_to :company
belongs_to :user
belongs_to :conversation
end
and my responders controller might it help:
class RespondersController < ApplicationController
before_action :all_responders, only: [:index, :create]
before_action :check_user
respond_to :html, :js
def show
#responder = Responder.find(params[:id])
end
def new
##conversation = Conversation.new
##conversation.save #Works, but puts data in database before submitting the form
#responder = Responder.new
end
def create
#responder = Responder.new(responder_params)
if #responder.save
redirect_to root_path
else
render partial: "form", errors: responder.errors.full_messages
end
end
private
def check_user
if !signed_in?
redirect_to new_session_path
end
end
def all_responders
if current_user
#companies = Company.all
if current_user.administrator?
#responders = Responder.where.not(:user_id => current_user.id)
else
#responders = Responder.where(:receiver => current_user.id)
end
end
end
def responder_params
params.require(:responder).permit(:title, :text, :receiver, :sender_id, :company_id, :user_id, :conversation_id)
end
end
The mail system is unique where there is a administrator that sends mails as multiple companies (from a drop down list) and users can only send mails to the company. I also don't want to use gems for this.
Thanks in advance!

Please try with before_create callback to create conversation in Responder class.

Related

With Ruby on Rails what's the best way to display data from a form field on a show page from another Ruby class?

I'm still somewhat new to Ruby and am having trouble displaying data on the show page from another class. I have two classes, Company and Job. On the Job show page I would like to display the Company's name, website and description from the Company form fields that created/posted the job when a job applicant views the respective job.
Was receiving an error when tinkering with the Job show controller action. Not entirely sure if the company is not being assigned an id when being created or if there's an issue with the show action login in the controller or a model association error on my end. Any help and explanation to resolve this issue is greatly appreciated.
Screenshot for Error Received on Job Show Page
Models
class Company < ApplicationRecord
has_many :jobs
has_many :job_applications, through: :jobs
class Job < ApplicationRecord
belongs_to :user
belongs_to :company, optional: true
has_many :job_applications, dependent: :destroy
class JobApplication < ApplicationRecord
belongs_to :user
belongs_to :job
Controllers
class CompaniesController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
# #company = self.create_company
end
# Only allow a list of trusted parameters through.
def company_params
params.require(:company).permit(:name, :website, :about, :user_id, :avatar)
end
class JobsController < ApplicationController
# GET /jobs/1 or /jobs/1.json
def show
#company = Company.find(params[:user_id])
# #company = Company.all
# #job = Job.find(params[:id])
end
Routes
resources :companies
resources :jobs
resources :jobs do
resources :job_applications
end
Job Show Page
<%= #company.name %>
<%= #company.website %>
<%= #company.about %>
I believe the problem lies in your show method in the JobsController.
It should look something like this:
class JobsController < ApplicationController
# GET /jobs/1 or /jobs/1.json
def show
#job = Job.find(params[:id])
#company = #job.company
end
This might throw some errors since you have optional: true in your relation. Also, I didn't care of n+1 queries since it's just a record, but this could be improved to be only 1 SQL query to the database.

Creating homes using nested routes

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.

Rails create action with multiple belongs_to

Trying to figure out a better way of assigning a review it's associated models.
I have the following classes:
class User < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :restaurant
end
class Restaurant < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
Pretty straightforward stuff. A review must have a restaurant and a user. My create action looks like this:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.build(review_params)
#review.user = current_user
if #review.save
redirect_to #restaurant
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:content)
end
Currently I build the review for the restaurant and then I assign the review's user to the current user.
This all works fine but is there a cleaner way to build the associations?
Is there a way to add additional arguments to the build method alongside the strong params?
I looked at accepts_nested_attributes_for but I couldn't get it to work.
Thanks!
You can use merge in the review_params like below
def review_params
params.require(:review).permit(:content).merge(user_id: current_user.id)
end
so that you can erase this line #review.user = current_user in the create method
In your form, you can put a hidden field with the user_id that you want to assign:
<%= f.hidden_field :user_id, value: #user.id %>
Then, add it to your review_params:
params.require(:review).permit(:content, :user_id)

Displaying Sender and Receiver user profile on each micropost

We are looking to have Sender and Receiver attributes for each micropost that is entered on our site. The sender of the post, and the receiver whom it is directed to.
In other words, on each micropost that each user sees, we want the content, and just above or below the content of the post we want the sender shown and receiver shown. We also want users to be able to click on either the sender or the receiver and be linked directly to that profile.
How can we go about doing this? We are relatively new to rails and think additions need to be made in the Micropost model for this change to work. Or should the changes be made in the MicropostsController?
Micropost Model:
class Micropost < ActiveRecord::Base
attr_accessible :content, :belongs_to_id
belongs_to :user
validates :content, :presence => true, :length => { :maximum => 240 }
validates :user_id, :presence => true
default_scope :order => 'microposts.created_at DESC'
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
end
MicropostsController:
class MicropostsController < ApplicationController
before_filter :authenticate, :only => [:create, :destroy]
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Posted!"
redirect_to current_user
else
#feed_items = []
render 'pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_path
end
end
To eliminate some confusion and make it a bit more railsy, I'd go with:
class Micropost < ActiveRecord::Base
belongs_to :sending_user, :class_name=>"User", :foreign_key=>"user_id"
belongs_to :receiving_user, :class_name=>"User", :foreign_key=>"belongs_to_id"
end
this will allow something like this in your view for a given Micropost object "#micropost":
<%= link_to(#micropost.sending_user.username, user_path(#micropost.sending_user)) %>
<%= link_to(#micropost.receiving_user.username, user_path(#micropost.receiving_user)) %>
*this assumes several things about the user object and routing, but should get you on the right path.

Having trouble with :dependent => :destroy and before_filter

Two models, an Account model (has_many :users) and a User model (belongs_to :account, :dependent => :destroy).
My User model has the following:
def protect_master_user
unless User.find_all_by_account_id_and_is_master(self.account_id, true).count > 1
false
end
end
I'm trying to protect the master users from being deleted. How can I override this if the parent (Account) is deleted? I've tried :dependent => :destroy and :delete to no avail.
EDIT: fixed code.
There are two ways to do this: has_many :users, :dependent => :delete_all, or with an additional custom callback in Accounts. The :delete_all method is simpler, but not advised, because it means that none of your other call backs will happen on the user record.
The correct solution is a custom before_destroy callback in Account, working in tandem with the callback in user.
class Account < ActiveRecord::Base
has_many :users
before_destroy :destroy_users
protected
def destroy_users
users.each do |user|
u.account_id = nil
u.destroy
end
end
end
class User < ActiveRecord::Base
belongs_to :account
before_destroy :protect_master_user
protected
def protect_master_user
unless account_id.nil? ||! master ||
User.find_all_by_account_id_and_master(self.account_id, true).count > 1
errors.add_to_base "Cannot remove master user."
return false
end
end
end
If the account.id is nil we short circuit the unless and the destroy continues. Same goes for if the user is not a master user. Why do we need to check if there's more than one master user if the object being destroyed isn't a master user either?
Again, delete could be used in place of destroy. But it skips any *_destroy callbacks you have or will ahve in the future.
I had this very same question and conundrum recently and found the best way to deal with this was to handle it in the controller as I really only care if a user is trying to delete the last Master user rather than if the system is doing it:
class UsersController < ApplicationController
def destroy
user = current_account.users.find(params[:id].to_i)
if allow_removal_of_user?(user) && user.destroy
redirect_to users_path, :notice => "User sucessfully deleted"
else
flash[:error] = user.errors.empty? ? "Error" : user.errors.full_messages.to_sentence
render :edit
end
end
private
def allow_removal_of_user?(user)
if user == current_user
errors.add(:user_removal, "Can't delete yourself")
false
elsif user.only_user? || (user.master_users_for_account.count == 1 && user.master_role?)
errors.add(:user_removal, "Can't delete last Master user")
false
else
true
end
end
end
Hope this helps somebody!

Resources