I am building an application that requires users to schedule appointment/conversations times with mentors. I am having a hard time building this out. I have been reading up on the has_many :through => Association but I know I am going about this the wrong way.
In my User.rb
Class User < ActiveRecord::Base
has_many :mentor_requests, foreign_key: "user_id"
has_many :mentors, through: :mentor_requests
def requested?(mentor)
mentor_requests.find_by_mentor_id(mentor.id)
end
def request!(mentor_request)
mentor_requests.create!(mentor_request)
end
def unrequest!(mentor)
mentor_requests.find_by_mentor_id(mentor.id).destroy
end
end
In my Mentor.rb
class Mentor < User
has_many :mentor_requests, foreign_key: "mentor_id"
has_many :users, through: :mentor_requests
end
In the Mentor_request.rb
class MentorRequest < ActiveRecord::Base
attr_accessible :reason, :mentor_id
belongs_to :user, class_name: "User"
belongs_to :mentor, class_name: "Mentor"
validates :user_id, :mentor_id, presence: true
validates :reason, presence:true, length: { maximum: 140 }
default_scope order: 'mentor_requests.created_at DESC'
end
In my requests controller is
def create
#mentor_request = current_user.mentor_requests.build(params[:mentor_request])
#current_user.request!(#mentor)
if #mentor_request.save
flash[:success] = "Your request has been sent"
redirect_to user_path(current_user)
#Send confirmations to both user and mentor
#Send the notification to an internal message inbox
else
render "new"
end
end
When I go to the view at mentor_requests/new.html.erb and try to submit the request it says that the mentor_id has to be present and the content has to be present. I tried creating a request using a modal view from the mentor show page but the content doesnt save and I validate that the presence has to be true, and then when it redirects to mentor_requests/new.html.erb the mentor id is no longer present.
I do not know if I have presented enough information but I seriously need help here. If I am on the right path what do I need to do to get it work, and if all this is wrong what do I do to get what I want.
Thanks a lot
Jude
make your mentor_request route nested under mentor. Since a mentor request doesn't make sense without the context of a mentor this is the perfect spot for a nested resource.
resources :mentors do
resources :mentor_requests
end
this will make your route something like /mentors/1/mentor_requests
And then in your controller you will also have a params[:mentor_id] so make it
def create
#mentor_request = current_user.mentor_requests.build(params[:mentor_request])
#mentor_request.mentor = Mentor.find(params[:mentor_id])
end
As for your content error, it seems like that is a field required on mentor_request that you arent' filling in. You'll need to pass that back too and assign it to the mentor_request or just take off the validation if you dont' need it
Related
I am building a simple expenses management app on rails 5.1.4. I am using the following five models.
Payees
class Payee < ApplicationRecord
has_many :expenses
validates :title, uniqueness: true, presence: true
end
Accounts
class Account < ApplicationRecord
before_save :update_balance
validates :balance, numericality: { greater_than_or_equal_to: 0 }
has_many :expenses
end
Budgets
class Budget < ApplicationRecord
belongs_to :categories
has_many :expenses, through: :categories
end
Categories
class Category < ApplicationRecord
validates :title, uniqueness: true, presence: true
has_many :expenses
has_one :budget
end
Expenses
class Expense < ApplicationRecord
belongs_to :categories
belongs_to :budgets
belongs_to :payees
belongs_to :accounts
validates :title, :value, presence: true
before_save :default_account
end
When I try to create a new expense I am facing a validation error
Validation failed: Categories must exist, Budgets must exist, Payees must exist, Accounts must exist
The issue is that all the above records exist. To explain my self let's say I am passing the params account_id: 1, payee_id: 1, category_id: 1. If I do:
Account.find(1) #=> Finds the record
Category.find(1) #=> also ok
Payee.find(1) #=> also ok
I am aware of the solution referred in this question (adding optional: true) but I don't get why I should do that while all of the above exist
Edit
The code that is raising the error is:
def create
#expense = Expense.create!(title: params[:expense]['title'],
value: params[:expense]['value'],
date: params[:expense]['date'],
comment: params[:expense]['comment'],
payee_id: params[:expense]['payee_id'],
category_id: params[:expense]['category_id'],
account_id: params[:expense]['account_id'])
end
The parameters that are passed through the form are
{"utf8"=>"✓",
"authenticity_token"=>"DWd1HEcBC3DhUahfOQcdaY0/oE+VHapxxE+HPUb0I6iSiqMxkz6l+vlK+1zhb66HnZ/vZRUVG4ojTdWUCjHtGg==",
"expense"=>{"title"=>"test", "value"=>"-20", "category_id"=>"1", "payee_id"=>"2", "date"=>"2018-01-21", "account_id"=>"1", "comment"=>""},
"commit"=>"Submit"}
I would first start by commenting out all your model validations, then creating an expense. Add back one model validation at a time, each time test creating an expense to see what validation is causing the error.
also you may want to change how you're creating the expense to something like below.
change your controllers create action to
def create
#expense = Expense.new(expense_params)
if #expense.save
flash[:success] = "expense created"
redirect_to expense_url(#expense.id)
else
render 'new'
end
end
next under your private method at the bottom of your controller you want to do something like this
private
# Never trust parameters from the scary internet, only allow the white list through.
def expense_params
params.require(:expense).permit(:title, :value, :date, etc...)
end
I finally found out where the problem is! It was the naming of the classes/models that raised the error. I had named my models on singular (Account, Category, etc) while all references are searching for plurals ( Accounts, Categories, etc). I had to re-do all migrations from the very beginning in order to make it work the proper way!
Thanks to everyone for spending the time though!
Terribly worded, but I'm confusing it.
I have a User model who has_many Clients and has_many statements, through: :clients and then statements which belongs_to clients and belongs to user
In Console I can do all the queries I want. User.statements User.client.first.statements etc - What I'm struggling on is Controller restrictions
For now it's simple - A user should only be able to see Clients and Statements in which they own.
For Clients I did
Client Controller
def index
#clients = Client.where(user_id: current_user.id)
end
Which seems to work perfectly. Client has a field for user_id
I'm kind of stuck on how to emulate this for Statements. Statements do -not- have a user_id field. I'm not quite sure I want them too since in the very-soon-future I want clients to belongs_to_many :users and Statements to not be bound.
Statement Controller
def index
#clients = Client.where(user_id: current_user.id)
#statements = Statement.where(params[:client_id])
end
I'm just genuinely not sure what to put - I know the params[:client_id] doesn't make sense, but what is the proper way to fulfill this? Am I going about it an unsecure way?
Client Model
class Client < ApplicationRecord
has_many :statements
has_many :client_notes, inverse_of: :client
belongs_to :user
validates :name, presence: true
validates :status, presence: true
accepts_nested_attributes_for :client_notes, reject_if: :all_blank, allow_destroy: true
end
Statement Model
class Statement < ApplicationRecord
belongs_to :client
belongs_to :user
validates :name, presence: true
validates :statement_type, presence: true
validates :client_id, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
end
User Model
class User < ApplicationRecord
has_many :clients
has_many :statements, through: :clients
end
Based on the reply provided below I am using
def index
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
#statements = #clients.statements
else
return 'error'
end
end
Unsure if this logic is proper
Use includes to avoid [N+1] queries.
And regarding "A user should only be able to see Clients and Statements in which they own".
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
# do more
else
# Type your error message
end
Additionally, you might need to use strong params and scope.
The best way to do it is using includes:
#clients = Client.where(user_id: current_user.id)
#statements = Statement.includes(clients: :users}).where('users.id = ?', current_user.id)
You can take a look in here: https://apidock.com/rails/ActiveRecord/QueryMethods/includes
In this case, thanks to the reminder that current_user is a helper from Devise, and the relational structure I showed, it was actually just as simple as
def index
#statements = current_user.statements
end
resolved my issue.
Due to the [N+1] Queries issue that #BigB has brought to my attention, while this method works, I wouldn't suggest it for a sizable transaction.
There are models with has has_many through association:
class Event < ActiveRecord::Base
has_many :event_categories
has_many :categories, through: :event_categories
validates :categories, presence: true
end
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
validates_presence_of :event, :category
end
class Category < ActiveRecord::Base
has_many :event_categories
has_many :events, through: :event_categories
end
The issue is with assigning event.categories = [] - it immediately deletes rows from event_categories. Thus, previous associations are irreversibly destroyed and an event becomes invalid.
How to validate a presence of records in case of has_many, through:?
UPD: please carefully read sentence marked in bold before answering.
Rails 4.2.1
You have to create a custom validation, like so:
validate :has_categories
def has_categories
unless categories.size > 0
errors.add(:base, "There are no categories")
end
end
This shows you the general idea, you can adapt this to your needs.
UPDATE
This post has come up once more, and I found a way to fill in the blanks.
The validations can remain as above. All I have to add to that, is the case of direct assignment of an empty set of categories. So, how do I do that?
The idea is simple: override the setter method to not accept the empty array:
def categories=(value)
if value.empty?
puts "Categories cannot be blank"
else
super(value)
end
end
This will work for every assignment, except when assigning an empty set. Then, simply nothing will happen. No error will be recorded and no action will be performed.
If you want to also add an error message, you will have to improvise. Add an attribute to the class which will be populated when the bell rings.
So, to cut a long story short, this model worked for me:
class Event < ActiveRecord::Base
has_many :event_categories
has_many :categories, through: :event_categories
attr_accessor :categories_validator # The bell
validates :categories, presence: true
validate :check_for_categories_validator # Has the bell rung?
def categories=(value)
if value.empty?
self.categories_validator = true # Ring that bell!!!
else
super(value) # No bell, just do what you have to do
end
end
private
def check_for_categories_validator
self.errors.add(:categories, "can't be blank") if self.categories_validator == true
end
end
Having added this last validation, the instance will be invalid if you do:
event.categories = []
Although, no action will have been fulfilled (the update is skipped).
use validates_associated, official documentaion is Here
If you are using RSpec as your testing framework, take a look at Shoulda Matcher. Here is an example:
describe Event do
it { should have_many(:categories).through(:event_categories) }
end
I'm sure I am just missing something simple, but have been racking my brain for the past few days over this.
I have a Booking and Review table, where the Booking has many Reviews. I can create the Review, but run through an error when trying to define roles of the user leaving and receiving the review.
Here are my models. The Review
class Review < ActiveRecord::Base
before_save :define_review_role
after_create :call_update_rating
belongs_to :booking
belongs_to :client, class_name: "User", primary_key: "client_id"
belongs_to :talent, class_name: "User", primary_key: "talent_id"
def define_review_role
if review_sender_id === self.booking.client_id
review_receiver_id = self.booking.talent_id
else
review_receiver_id = self.booking.client_id
end
self.update
end
def call_update_rating
user = User.find(self.review_receiver_id)
if review_receiver_id == self.booking.talent_id
user.update_talent_rating(self.rating)
else
user.update_client_rating(self.rating)
end
user.save
end
end
And the Booking model
class Booking < ActiveRecord::Base
# Start Validations
validates :amount, format: {with: /(\d{1,3})(\.\d{1,2})?/, :message => "field is invalid. Please enter a correct amount."}
belongs_to :user
belongs_to :client, class_name: "User", primary_key: "client_id"
belongs_to :talent, class_name: "User", primary_key: "talent_id"
has_many :reviews
has_many :sent_reviews, class_name: "Review", primary_key: "talent_id"
has_many :received_reviews, class_name: "Review", primary_key: "client_id"
def client
User.find(client_id)
end
def talent
User.find(talent_id)
end
end
I have been able to create the review just fine, and upon inspection I am finding that the review.review_receiver_id is being left blank. The define_review_role for some reason is not running, I have tried with before_create, after_save, after_create and no dice.
I know that this is not running because upon inspection, the review_receiver_id is being left blank.
I am also able to access the information through review.booking.talent_id, and review.booking.client_id, so the connections are there. I know I must be missing something but have no idea what.
Your method define_review_role running you only have badly written code. It should probably look something like this
def define_review_role
if review_sender_id === self.booking.client_id
self.review_receiver_id = self.booking.talent_id
else
self.review_receiver_id = self.booking.client_id
end
end
If you try to assign value without self the value is assigned to newly created local method instead of attribute of your Report class.
You also cannot call save or update on the end of this method because you are calling it with before_save callback. Methods save and update trigger it again and method gonna be called again and you create infinity loop.
I'm having trouble with Active Record callbacks in a model which contains accepts_nested_attributes
I call build_associated_partiesusing an after_create callback, but these values are not being saved and I get <nil> errors. I've also tried using before_create & after_initialize callbacks without success.
What is causing the callbacks to fail?
connection.rb
class Connection < ActiveRecord::Base
attr_accessible :reason, :established, :connector, :connectee1,
:connectee2, :connectee1_attributes,
:connectee2_attributes, :connector_attributes
belongs_to :connector, class_name: "User"
belongs_to :connectee1, class_name: "User"
belongs_to :connectee2, class_name: "User"
accepts_nested_attributes_for :connector, :connectee1, :connectee2
belongs_to :permission
after_create :build_associated_parties
# builds connectee's, connector, permission objects
def build_associated_parties
build_connector
build_connectee1
build_connectee2
build_permission
end
connection_controller.rb
class ConnectionsController < ApplicationController
def new
#connection = Connection.new
end
def create
#connection = Connection.new params[:connection]
if #connection.save
flash[:notice] = "Connection created successfully!"
redirect_to #connection
else
render :new
end
end
end
However, if I instead build these attributes inside the controller as shown below, I don't get the error. This is nice, but it seems to go against keeping business logic code out of the controller.
class ConnectionsController < ApplicationController
def new
#connection = Connection.new
#connection.build_connectee1
#connection.build_connectee2
#connection.build_connector
end
end
How can I accomplish the same functionality with code in the model? Are there advantages to keeping it in the model?
You called your method build_associated_parties after connection is created, so how these methods:
build_connector
build_connectee1
build_connectee2
build_permission
know what params it will use? So they don't know what values are passed into method then they will get error. In controller, they didn't have error because they used values of params[:connection].
On your form, if you already have fields for connector, connectee1, connectee2, you should put code which initialize object in your new controller. When you save #connection, it's saved those object too. I think these codes aren't need to put into model. Your model only should put other logic code, like search or calculation...
after_create is a big no. Use after_initialize in your model and use self inside your build_associated_parties method. See if that works.
Moved logic out of controller and back into model. However, the build_* code was overwriting the values I was passing into the nested attributes.
By adding unless {attribute} to these build_ methods, I was able to properly pass in the values.
class Connection < ActiveRecord::Base
attr_accessible :reason, :established, :connector, :connectee1, :connectee2,
:connectee1_attributes, :connectee2_attributes, :connector_attributes
belongs_to :connector, class_name: "User"
belongs_to :connectee1, class_name: "User"
belongs_to :connectee2, class_name: "User"
accepts_nested_attributes_for :connector, :connectee1, :connectee2
belongs_to :permission
after_initialize :build_associated_parties
validates :reason, :presence => true
validates_length_of :reason, :maximum => 160
#builds connectee's, connector, permission objects
def build_associated_parties
build_connector unless connector
build_connectee1 unless connectee1
build_connectee2 unless connectee2
end