Activerecord "where" condition for Array attribute - mysql

I have model course.rb with pre_courses is an array.
class Course < ActiveRecord::Base
serialize :pre_courses, Array
end
Now I want to check if an exist course is pre_course of any course by Activerecord or raw SQL (I am using MySQL), such as Course.where("pre_courses INCLUDEs self.id").
Is there any way to do this?

A serialized array is just a string in the db, so try using LIKE, for example:
Course.where("pre_courses LIKE ?", "% #{self.id}\n%")
Notice that a serialzed array adds a space before each item and a new line after, thus the added space before the interpolated string and the \n at the end.

It sounds like a pre_course is actually a regular course, a course can have many pre_courses and a pre_course can belong to many courses. A self referential has_many through relationship is possible and may give you more flexibility to work with your data than serializing it as an array.
You'll need a join model I'll call CoursePreCourse. It will have the columns course_id and pre_course_id. pre_course_id will be a foreign key for records on the courses table.
class CreateCoursePreCourses < ActiveRecord::Migration[5.1]
def change
create_table :course_pre_courses do |t|
t.references :course, foreign_key: true
t.references :pre_course, foreign_key: { to_table: :courses }
t.timestamps
end
end
end
class CoursePreCourse < ApplicationRecord
belongs_to :course
belongs_to :pre_course, class_name: 'Course'
end
class Course < ApplicationRecord
# A straight-forward has_many :through association for a course that has_many :pre_courses
has_many :course_pre_courses
has_many :pre_courses, through: :course_pre_courses
# A little coercion is necessary to set up the association as a pre_course that has_many :courses
has_many :pre_course_courses, class_name: 'CoursePreCourse', foreign_key: :pre_course_id
has_many :courses, through: :pre_course_courses
end
Now you can retrieve the courses that are pre_courses of any course with course.pre_courses. If you want to see if a course is a pre_course of other courses it's pre_course.courses. Similarly you can add a course to another course's pre_courses with either course.pre_courses << pre_course or pre_course.courses << course.
Do you ever say a word so many times it loses its meaning?

Related

How to make associations on this

Hello, i'm new to rails. I have been fed up with active record associations. I did study associations from the rails guides. Yet i cant find myself a clear way to add associations to the models suggested in the diagram.
I have one doubt whether a single foreign key(SECOND MODEL) can reference two primary keys (SECOND MODEL LEVEL 2 FIRST & SECOND MODEL LEVEL 2 SECOND). This has been done because the user has to choose whether to add from the SECOND MODEL LEVEL 2 FIRST TABLE or the SECOND MODEL LEVEL 2 SECOND TABLE while inserting values into the SECOND MODEL.
If u find this hard to understand please leave a comment, ill make appropriate changes. And i would appreciate on how to query from the FINAL LEVEL FIRST with BASE-MODEL thorough a join condition.
You can use polymorphic association to refer to one table OR another, check the model below as per the posted image:
class BaseModel
has_many :first_models
has_many :second_models
end
class FirstModel
belgons_to :base_model
has_one :level_two_first_model
end
class LevelTwoFirstModel
belgons_to :first_model
end
class SecondModel
belgons_to :base_model
has_many :final_first_levels, as: :referenceable, :dependent => :destroy
has_many :final_second_levels, as: :referenceable, :dependent => :destroy
end
class LevelOneSecondModel
belongs_to :referenceable, polymorphic: true
has_many :final_first_levels
end
class LevelTwoSecondModel
belongs_to :referenceable, polymorphic: true
has_many :final_first_levels
end
class FinalFirstLevel
belongs_to :LevelOneSecondModel
end
class FinalSecondLevel
belongs_to :LevelTwoSecondModel
end
referenceable is used as the glue between the parent table and other polymorphic associations (LevelOneSecondModel OR LevelTwoSecondModel)
NB:
Don't forget to add the below line in the migration files of the 2 children tables used in polymorphic association.
t.references :referenceable, polymorphic: true, index: true
Reference:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Rails association - Customised list of items from existing list

I am developing a rails app where users can add tasks they wish to do to a customised list of their own. Each task can also belong to 0 or more categories. So far I've tried this:
user.rb
has_one :user_list
has_many :tasks, through: :user_list
user_list.rb
belongs_to :user
has_many :tasks
tasks.rb
has_and_belongs_to_many :categories
[timestamp}_migration.rb
create_table :user_lists do |t|
t.integer :user_id
t.integer :task_id
t.timestamps null: false
end
The issue I am having is in the console I try to run User.find(1).tasks it cannot find the column tasks.user_list_id when using the following query:
SELECT "tasks".* FROM "tasks" INNER JOIN "user_lists" ON "tasks"."user_list_id" = "user_lists"."id" WHERE "user_lists"."user_id" = ? [["user_id", 1]]
This query should be joining the tasks id from the tasks table with the tasks id on the user_lists table. Are the associations correct and if so what can I do to change the query?
To allow tasks to be placed on many lists you need a M2M join table which joins the user_lists and tasks table.
class User < ActiveRecord::Base
has_many :user_lists
has_many :tasks, through: :user_lists
end
class Task < ActiveRecord::Base
has_many :user_list_items
has_many :user_lists, through: :user_list_items
end
class UserListItem < ActiveRecord::Base
belongs_to :task
belongs_to :user_list
has_one :user, through: :user_list
# optional
validates_uniqueness_of :task_id, scope: :user_list_id
end
class UserList < ActiveRecord::Base
belongs_to :user
has_many :user_list_items
has_many :tasks, through: :user_list_items
end
You can create the join model and migration with:
rails g model UserListItem user_list:belongs_to task:belongs_to
You may also want to open up the migration and add a compound index:
add_index :user_list_items, [:user_list_id, :task_id], unique: true
Setting it as unique is optional - but in most cases you want join table entries to be unique for table A and B.
See:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
http://guides.rubyonrails.org/active_record_migrations.html
Your use case calls for a task to be assigned to more than one user and a user has only one task list. This sounds like a HABM association between users and tasks.
The simplest way to express that would be:
class User
has_and_belongs_to_many: :tasks
...
end
class Task
has_and_belongs_to_many: :users
...
end
and a migration to create the join table:
create_join_table :users, :tasks, do |t|
t.index :user_id
t.index.task_id
end
You don't need to create a TaskUser model to match the join table until you need to track additional attributes. Rails will take care of that automatically.
If a user needs more than one task list, you'll need that TaskList model. Let me know and I'll update my answer.
Here's the documentation on HABM and join migration

Creating nested records on create with Rails

I have a projects model that I am using to auto generate departments within a specific project on create. This is included in the projects model with:
class Project < ActiveRecord::Base
attr_accessible :title, :departments_attributes, :positions_attributes, :id
belongs_to :user
has_many :departments
has_many :positions
validates :title, presence: true
before_create :set_departments
accepts_nested_attributes_for :departments
accepts_nested_attributes_for :positions
private
def set_departments
self.departments.build department: "Test Dept", production_id: self.id
end
end
Each department has many positions. I am trying to create positions as well for the departments. How could I associate a new position with a department in this model?
There are two ways:
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments
before_create :set_department
private
def set_department
self.departments.build department: "Test"
end
end
#app/models/department.rb
class Department < ActiveRecord::Base
has_many :positions
accepts_nested_attributes_for :positions
before_create :set_positions
private
def set_positions
self.positions.build x: y
end
end
... or ...
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments, :projects
before_create :set_departments
private
def set_departments
dpt = self.departments.build department: "Test"
dpt.positions << Position.new position: "Admin"
dpt.positions << Position.new position: "Tester"
end
end
--
You can also declare multiple nested attributes on a single line:
accepts_nested_attributes_for :departments, :positions
If I understand your question correctly, you might do something like this in your Department model:
after_create { self.positions.create! }
Though this might be a problematic approach. Creating records like this using ActiveRecord callbacks (which is what gives us after_create) can make your whole app really fragile. For example, if you do this, you'll never be able to make a department without an associated position. Perhaps one day you'll need to do just that.
So even though it's not the exact answer to your question, I suggest looking at created these associated models in a service object, or at least in controller code.
You can add a new position with:
Project.first.positions << Position.create(:foo => 'foo', :bar => 'bar')
or
position = Position.create(:foo => 'foo', :bar => 'bar')
Department.first.positions << position
Project.first.positions << position
Obviously the ".first" is just for illustration and you can use the << notation with any Department or Project instance.
Looking at this again, it seems like a really good fit for polymorphic associations.
class Position < ActiveRecord::Base
belongs_to :positioned, polymorphic: true
end
class Project < ActiveRecord::Base
has_many :positions, as: :positioned
end
class Department < ActiveRecord::Base
has_many :positions, as: :positioned
end
And in your migration:
create_table :positions do |t|
...
t.integer :positioned_id
t.string :positioned_type
...
end
There may be a more suitable way to name things for your app but this is the general idea.

Validate uniqueness of both ids in a join table

I have a list model which has_and_belongs_to_many postings and vice versa.
I have a join table:
create_table :postings_lists do |t|
t.integer :posting_id
t.integer :list_id
end
Users have many lists.
I already validate the list uniqueness for the user with:
validates :name, presence: true, uniqueness: {scope: :user_id}
In the join table, how can I validate the uniqueness of both the :posting_id and the :list_id so that a posting can't belong to a list more than once?
I have tried adding, uniq: true to both has_and_belongs_to_manys in the models but it messes things up and I have tried added custom validations in the list model but it wasn't working.
I think the simplest thing would be just to validate both ids in the join table but I don't know if I can do that without creating a model?
I would use a has_many :through instead of HABTM.
class List < ActiveRecord::Base
has_many :postings
has_many :posts, through: :postings
end
class Posting < ActiveRecord::Base
belongs_to :list
belongs_to :post
validate :post_id, uniqueness: {scope: :list_id}
end
class Post < ActiveRecord::Base
has_many :postings
has_many :lists, through: postings
end
I ended up adding uniq: true
Lists
has_and_belongs_to_many :postings, uniq: true
Postings
has_and_belongs_to_many :lists, uniq: true
This causes some issues if you use things like pluck on postings because things have to be "distinct" before doing other things like "order_by" to them.
But you can work around this issue by using different queries like 'map' instead.

Multiple Foreign Keys with Ruby on Rails

I have the following setup:
One matchdays table with a column called home_team_id and one called visitor_team_id
and a team table.
My Match model looks like this:
class Match < ActiveRecord::Base
belongs_to :home_team, class_name: "Team", foreign_key: :home_team_id
belongs_to :visitor_team, class_name: "Team", foreign_key: :visitor_team_id
belongs_to :matchday
validates :home_team, presence: true
validates :visitor_team, presence: true
end
And the Team model like that:
class Team < ActiveRecord::Base
has_many :matches
has_many :player
end
Now it's getting tricky (at least for me). I'd like to be able to call team.matches and get all of the matches for the team. Since every team has home games and also games on the road.
Currently I'm getting a ActiveRecord::StatementInvalid Error because it's looking for the team_id column in the matches table.
So if I understand correctly, what you need is just a method that returns all games where the current team is playing. This should do the trick:
class Team < ActiveRecord::Base
has_many :player
def matches
Team.where(home_team_id => self.id, foreign_key => self.id)
# This line will also work if you want to try it out.
# Team.where("home_team_id = ?", self.id).where("foreign_key = ?", self.id)
end
end
Happy coding!