ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection Error - ruby-on-rails

I have a model Job, which has_many Applications, and has_many Questions. An Answer belongs to both an Application and a Question.
I'm trying to make a factory method an admin can use to create applications, without users having to write anything.
To do this, I wrote --
def self.make_by_admin(params)
app = Application.new
app.user_id = params[:user_id]
app.job_id = params[:job_id]
app.questions.each do |question|
app.answers.new(question_id: question.id, content: 'N/A')
end
app
end
But, I get the error
#<ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'Application#questions' because the source reflection class 'Question' is associated to 'Job' via :has_many.>
What's weird though is that I'm not actually modifying questions. All I want to do is to generate blank answers for each question.
How would I go about doing that?
Full Models
class Job < ActiveRecord::Base
default_scope { order('jobs.created_at DESC') }
has_many :bullets, dependent: :destroy, inverse_of: :job
has_many :roles, dependent: :destroy, inverse_of: :job
has_many :questions, dependent: :destroy, inverse_of: :job
has_many :job_city_relations, inverse_of: :job, dependent: :destroy
has_many :cities, through: :job_city_relations
has_many :job_industry_relations, inverse_of: :job, dependent: :destroy
has_many :industries, through: :job_industry_relations
has_many :applications, inverse_of: :job, dependent: :destroy
has_many :users, through: :applications
validates :cities,
:job_title,
:job_summary,
:qualifications,
:industries,
:bullets,
:roles,
:questions,
presence: true
accepts_nested_attributes_for :bullets,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :roles,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :questions,
reject_if: :all_blank,
allow_destroy: true
scope :with_cities, ->(city) do
job = Job
.includes(:industries)
.includes(:cities)
.includes(:questions)
.includes(:bullets)
.includes(:roles)
job = job.where(cities: { id: city }) if city
job
end
scope :with_search, ->(search) do
job = Job.includes(:industries)
.includes(:cities)
.includes(:bullets)
.includes(:roles)
.includes(:questions)
if search
job = job.where('jobs.job_title LIKE ?', "%#{search}%")
end
job
end
scope :with_info, -> do
Job.includes(:industries)
.includes(:cities)
.includes(:bullets)
.includes(:roles)
.includes(:questions)
end
def self.build
job = Job.new
2.times {
job.bullets.build
job.roles.build
}
job.questions.build
job
end
def potentials
good_fits = User.includes(:source, :heat, :applications, common_app: [:cities, :industries])
.where('cities.id IN (?)', self.city_ids)
.where('industries.id IN (?)', self.industry_ids)
.where('users.id NOT IN (?)', self.users.map(&:id))
end
end
class Application < ActiveRecord::Base
STATUS_OPTIONS = ["Application Complete",
"Materials Submitted",
"Pending Interview",
"Second Interview"]
belongs_to :job, counter_cache: true
belongs_to :user, counter_cache: true
has_many :questions, through: :job
has_many :answers, inverse_of: :application, dependent: :destroy
validates :job_id, presence: true
validates :user_id, presence: true
validates :answers, presence: true
accepts_nested_attributes_for :answers,
allow_destroy: true, reject_if: :all_blank
scope :with_dependents, -> do
Application
.includes(:job)
.includes(:questions)
.includes(:answers)
end
scope :for_job, ->(job_id) do
Application
.includes(user: [:source, :heat, common_app: [:cities, :industries]])
.includes(questions: :answer)
.where('applications.job_id = ?', job_id)
end
def self.build(job, appl = Application.new)
job.questions.each do |question|
appl.answers.build(question_id: question.id)
end
appl
end
def self.make_by_admin(params)
app = Application.new
app.user_id = params[:user_id]
app.job_id = params[:job_id]
app.questions.each do |question|
app.answers.new(question_id: question.id, content: 'N/A')
end
fail
app
end
end
class Question < ActiveRecord::Base
belongs_to :job
has_one :answer
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :application
end

I ended up writing
def self.make_by_admin(params)
app = Application.new
app.user_id = params[:user_id]
app.job_id = params[:job_id]
app.questions.pluck(:id).each do |id|
app.answers.new(question_id: id, content: 'N/A')
end
app
end

Related

Model.find() calling a different model

I have 3 models user, micropost, like
I followed Ruby on Rails Tutorial by Michael Hartl
https://rails-4-0.railstutorial.org/book
Now I am adding new features and was trying to add a like button and after some changes now my micropost delete button doesn't work.
#micropost_controller
def destroy
Micropost.find(params[:id]).destroy
redirect_to root_url
end
#_micropost.html.erb
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" },
title: micropost.content %>
Even though i am calling Micropost.find Active Record searches in likes table and gives the error as
ActiveRecord::StatementInvalid in MicropostsController#destroy
SQLite3::SQLException: no such column: likes.micropost_id: SELECT "likes".* FROM "likes" WHERE "likes"."micropost_id" = ?
Similar thing happens when i try to delete a user. So basically whichever destroy i am trying to call it is redirected to like model
PS: the problem started after i executed
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
as i was getting BusyException: database is locked: commit transaction
models
#like.rb
class Like < ApplicationRecord
belongs_to :microposts, optional: true
belongs_to :users, optional: true
validates :src_user_id, presence: true
validates :des_user_id, presence: true
validates :post_id, presence: true
end
#micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :content, presence: true, length: { maximum: 140 }
validates :user_id, presence: true
has_many :likes, dependent: :destroy
# Returns microposts from the users being followed by the given user.
def self.from_users_followed_by(user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
user_id: user.id)
end
def self.search(search)
where("content LIKE ?", "%#{search}%")
end
end
#user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
has_many :likes, foreign_key: "src_user_id", dependent: :destroy
has_many :liked_by_users, through: :likes, source: :likes
scope :starts_with, -> (name) { where("name like ?", "#{Example User}%")}
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, unless: Proc.new { |user| user.password.nil? }
validates :password_confirmation, presence: true, unless: Proc.new { |user| user.password.nil? }
validates :birthday, presence: true
validate :email_verification, on: :update
end
class User
has_many :microposts, dependent: :destroy
# You never have to specify foreign key for indirect assocations
has_many :likes, through: :microposts
end
class Micropost
belongs_to :user
has_many :likes, dependent: :destroy, foreign_key: "post_id"
end
class Like
# You must specify the foreign key if it cannot be derived from the name of the association
# `belongs_to :micropost` will use micropost_id
belongs_to :micropost, optional: true, foreign_key: "post_id"
belongs_to :user # you have to add a `likes.user_id` column
end
Although I would really question if you want to use the same table/model for user likes and micropost likes. It really just makes things a lot more complicated and you end up with a table with a large number of null values. If this is part of the book I really wonder what the author was thinking as its a really bad design.
You can just set it up as:
# app/models/users/like.rb
# table name is users_likes
module Users
class Like
belongs_to :src_user, class_name: 'User', optional: false
belongs_to :des_user, class_name: 'User', optional: false
end
end
class User
has_many :likes_as_src,
class_name: 'Users::Like',
foreign_key: :src_user
has_many :likes_as_des,
class_name: 'Users::Like',
foreign_key: :src_user
end
# app/models/users/like.rb
# table name is microposts_likes
module Microposts
class Like
belongs_to :user, optional: false
belongs_to :micropost, optional: false, foreign_key: 'post_id'
end
end
class User
has_many :micropost_likes, class_name: 'Microposts::Like'
has_many :liked_microposts, through: :micropost_likes, source: :micropost
end
That gives you two simple join tables with non-nullable foreign keys and good indices. It also makes the validations very straight forward.
Correct associations would be
#user.rb
has_many :microposts, dependent: :destroy
has_many :likes, through: :microposts, foreign_key: "post_id"
#micropost.rb
belongs_to :user
has_many :likes, dependent: :destroy, foreign_key: "post_id"
#like.rb
belongs_to :micropost, optional: true
After this, it works fine

How to check if all lessons have been viewed

I want to define a method (complete) that returns true if a user has watched all lessons of a course. Right now the method only returns false even if all the lessons are viewed.
User model:
has_many :enrolments, dependent: :destroy
has_many :courses, through: :enrolments
has_many :views
has_many :lessons, through: :views
def enrol(course)
courses << course
end
def enrolled?(course)
courses.include?(course)
end
def view(lesson)
lessons << lesson
end
def viewed?(lesson)
lessons.include?(lesson)
end
def complete(course)
if self.viewed?(course.lessons.all)
return true
else
return false
end
end
Lesson Model:
belongs_to :course
has_one_attached :file
default_scope -> { order(created_at: :desc) }
has_many :views
has_many :users, through: :views
Course Model:
belongs_to :company
has_many :lessons, dependent: :destroy
default_scope -> { order(created_at: :desc) }
has_many :enrolments, dependent: :destroy
has_many :users, through: :enrolments
View Model:
belongs_to :user
belongs_to :lesson
Your viewed? checks only for a single lesson, but you're passing a relation there.
For this case it's sufficient to compare arrays with lessons ids:
def complete(course)
lessons.where(course: course).ids.sort == course.lessons.ids.sort
end

Rails saving nested model association, has_many through

class Task < ActiveRecord::Base
belongs_to :project, :inverse_of => :tasks
accepts_nested_attributes_for :project
end
class Project < ActiveRecord::Base
belongs_to :workspace, :inverse_of => :projects
has_many :tasks, :inverse_of => :project,
dependent: :destroy,
autosave: true
accepts_nested_attributes_for :workspace
end
class Workspace < ActiveRecord::Base
has_many :projects, inverse_of: :workspace,
dependent: :destroy,
autosave: true
has_many :tasks, through: :projects
end
I am able to save the child object 'project' from 'workspace'. But I can't save the grandchild object 'task' from 'workspace'. What's wrong? How can I do that without additional '.save' calls? I don't want to call #p.save
####child####
#w = Workspace.first
#p = #w.projects.new(name: "text")
#w.save
#p.new_record? #false, which is ok
###grandchild###
#w = Workspace.first
#p = #w.projects.first
#t = #p.tasks.new(name: "text")
#w.save
#t.new_record? #true, but should've been saved

Rails custom validation on has_many association

Given the following models:
Business
class Business < ActiveRecord::Base
## OWNERS / EMPLOYEES
has_many :business_users, as: :business, dependent: :destroy
has_many :users, through: :business_users
accepts_nested_attributes_for :business_users, allow_destroy: true, reject_if: lambda { |a| a[:user_id].blank? || a[:role].blank? }
end
BusinessUser
class BusinessUser < ActiveRecord::Base
belongs_to :business, polymorphic: true
belongs_to :user
enum role: {:owner => 0, :employee => 1, :admin => 2}
validates_presence_of :user
validates :user, uniqueness: { scope: :business, message: "Each user can only have one role" }
end
User
class User < ActiveRecord::Base
has_many :business_users
has_many :businesses, through: :business_users, source_type: Business, source: :business
end
How can i make sure that each business has at least one business_user with role :owner?

Polymorphic Associations Status in concern

I created concern in which placed association.
I need to make polymorphic association status.
has_one :status, class_name: 'VideoStatus', inverse_of: :video, dependent: :destroy
belongs_to :video, inverse_of: :status
I can't make that Association.How to make it?
require 'active_support/concern'
module EncodeStatuses
extend ActiveSupport::Concern
FORMATS = %w[mp4]
HIGH_VERSION = 'high'
MEDIUM_VERSION = 'medium'
LOW_VERSION = 'low'
VERSIONS = [HIGH_VERSION, MEDIUM_VERSION, LOW_VERSION]
included do
has_one :status, class_name: 'VideoStatus', inverse_of: :video, dependent: :destroy
accepts_nested_attributes_for :status, update_only: true
delegate :success?, :failure?, :waiting?, :encoding?, to: :status
end
end
models/video.rb
class Video < ActiveRecord::Base
include EncodeStatuses
...
end
models/post.rb
class Post < ActiveRecord::Base
include EncodeStatuses
...
end
models/video_status.rb
class VideoStatus < ActiveRecord::Base
STATUS_WAITING = 'waiting'
STATUS_ENCODING = 'encoding'
STATUS_SUCCESS = 'success'
STATUS_FAILURE = 'failure'
STATUSES = [STATUS_WAITING, STATUS_ENCODING, STATUS_SUCCESS, STATUS_FAILURE]
belongs_to :video, inverse_of: :status
belongs_to :post, inverse_of: :status
...
end
has_one :status, as: :video_status, dependent: :destroy
class VideoStatus < ActiveRecord::Base
belongs_to :video_status, polymorphic: true
end

Resources