Search In a column which stores an array - mysql

In my table professional_infos table there is one column primary_skill_ids which stores an array of skills for particular user. I want to list all the user who has some particular skill sets.
example -
user1 has primary_skill as ["1","3","5","9","4"]
user2 has primary_skill as ["1","7","9","4"]
user3 has primary_skill as ["1","4","11"]
user3 has primary_skill as ["7","9","4"]
user4 has primary_skill as ["1","7","9"]
user5 has primary_skill as ["7","9"]
now I want to perform a search like get me all the users which have any or all of the skill primary_skill_ids as ["1","4]
please help me to write a rails query to do so.
I have done like the following
wildcard_search = "%#{params[:search_title]}%"
# key skills and best in search
#key_skills=[]
#key_skills.each do | sk |
# here I am thinking of looping through the ids and do a where clause on column primary_skill_ids but dont know its good idea
end

Serializing relation data in a string column violates the very idea of a relational database - which is that you have foreign key columns that point to other tables. Using array* or string types in the DB for associations is a really bad idea:
It is highly inefficient to search strings compared to an indexed column containing integers or uiids.
There is no referential integrity enforced by foreign key constraints.
Its not how ActiveRecord works - which means you'll be wasting time by fighting the framework.
Instead you want to create a many-to-many association through a join table:
class User < ApplicationRecord
has_many :user_skills
has_many :skills, through: :user_skills
end
class Skill < ApplicationRecord
has_many :user_skills
has_many :users, through: :user_skills
end
class UserSkill < ApplicationRecord
belongs_to :user
belongs_to :skill
end
In this example we are using a table named user_skills to join the two models:
create_table "user_skills", force: :cascade do |t|
t.integer "user_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["skill_id"], name: "index_user_skills_on_skill_id", using: :btree
t.index ["user_id"], name: "index_user_skills_on_user_id", using: :btree
end
You can then setup UI controls by using the collection helpers:
# app/views/users/_form.html.erb
<%= form_for(#user) do |f| %>
<%= f.collection_check_boxes :skill_ids, Skill.all, :id, :name %>
<% end %>
# app/controllers/users_controller.rb
class UsersController < ApplicationController
# POST /users
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render :new
end
end
# PUT|PATCH /users/:id
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render :edit
end
end
def user_params
params.require(:user)
.permit(skill_ids: [])
end
end

PostgreSQL supports standard SQL arrays and the standard any operation syntax:
So, you can use an SQL like
where name ilike any (array['%val1%', '%val2%'])
Using Rails it can be written like:
User.where('primary_skill ilike any (array[?])', ["1","4"] )
Additional info:
In your situation you can use "ILIKE", or "LIKE", but the difference is:
"ILIKE" is case-insensitive
"LIKE" is case-sensitive.
Usage of wildcards (% from '%val1%') are detailed here

Related

I have a duplicate column name error using MySql when trying to deploy Rails app

ActiveRecord::StatementInvalid: Mysql2::Error: Duplicate column name 'first_name': ALTER TABLE `users` ADD `first_name` varchar(255)
This is the error I am receiving when I am trying to deploy my Rails app using ShipIt.
My question is: how do I resolve this error?
I started off my application with the user having an attribute called name, and then split up name into first_name and last_name in the following migration. When I reset my database (rails db:reset) everything works fine. When I drop my database, create it, migrate my changes, and seed manually everything looks dandy. My schema looks like it contains only the data that I want. I am confused. When I query the tables using sql and rails, the columns look fine.
/app/db/migrate/20180507080705_break_up_fullname_in_users.rb:3:in `up'
That is where it says the error is caused.
This is the entire migration which seems to cause the error:
class BreakUpFullnameInUsers < ActiveRecord::Migration[5.2]
def up
add_column :users, :first_name, :string
add_column :users, :last_name, :string
User.all.each do |user|
fn, ln = name.split(' ', 2)
user.update(first_name: fn, last_name: ln)
end
remove_column :users, :name
end
def down
add_column :users, :name, :string
User.all.each do |user|
n = [name.first_name, name.last_name].join(' ')
user.update(name: n)
end
remove_column :users, :first_name
remove_column :users, :last_name
end
end
And this was the original migration to create the table:
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email
t.string :name
t.string :username
t.string :password_digest
t.timestamps
end
end
end
This is my in my schema file under users:
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "email"
t.string "username"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "first_name"
t.string "last_name"
t.boolean "admin", default: false
end
The error is coming because the columns first_name and last_name already been added/committed to the table in database. And also name column from users table is removed.
The migration execution already completed and somehow rails has failed to update migration version in schema_migrations table might be due to the code that is updating data to two columns first_name and last_name from name field.
So I would suggest comment the all lines present in up method and re deploy the file to the server => run the migrations and start the server.
def up
#add_column :users, :first_name, :string
#add_column :users, :last_name, :string
#User.all.each do |user|
# fn, ln = name.split(' ', 2)
#user.update(first_name: fn, last_name: ln)
#end
#remove_column :users, :name
end

how to add multiple foreign keys into the same row in Many to many in Ecto

Is it possible to insert multiple many to many relations at one? for example I have an AccessList table which has company_id, user_id, role_id, asset_id, and project_id
They are all many to many. AccessList is the reference table.
Here is the schema for the AccessList
schema "access_lists" do
belongs_to :user, Db.User
belongs_to :role, Db.Role
belongs_to :asset, Db.Asset
belongs_to :project, Db.Project
belongs_to :company, Db.Company
timestamps()
end
Here is the company schema as an example:
many_to_many :users, Db.User, join_through: Db.AccessList
many_to_many :assets, Db.Asset, join_through: Db.AccessList
many_to_many :roles, Db.Role, join_through: Db.AccessList
many_to_many :projects, Db.Project, join_through: Db.AccessList
At the moment for example if a request comes in I will, get the user, role, and company, then I create the asset and I insert their IDs at once into AccessList.
Sample:
def create_asset_relation(role, asset, user, company) do
changeset = create_asset_changeset( %{user_id: user.id, role_id: role.id, company_id: company.id, asset_id: asset.id})
with {:ok, _ } <- Repo.insert(changeset) do
{:ok, asset}
else
_ ->
"something gone wrong"
end
end
defp create_asset_changeset(params) do
AccessList.changeset(%AccessList{}, params)
end
Is it possible to insert multiple many to many relations at one?
Yes, your code example resembles the join schema example from the documentation:
defmodule UserOrganization do
use Ecto.Schema
#primary_key false
schema "users_organizations" do
belongs_to :user, User
belongs_to :organization, Organization
timestamps # Added bonus, a join schema will also allow you to set timestamps
end
def changeset(struct, params \\ %{}) do
struct
|> Ecto.Changeset.cast(params, [:user_id, :organization_id])
|> Ecto.Changeset.validate_required([:user_id, :organization_id])
# Maybe do some counter caching here!
end
end
defmodule User do
use Ecto.Schema
schema "users" do
many_to_many :organizations, Organization, join_through: UserOrganization
end
end
defmodule Organization do
use Ecto.Schema
schema "organizations" do
many_to_many :users, User, join_through: UserOrganization
end
end
# Then to create the association, pass in the ID's of an existing
# User and Organization to UserOrganization.changeset
changeset = UserOrganization.changeset(%UserOrganization{}, %{user_id: id, organization_id: id})
case Repo.insert(changeset) do
{:ok, assoc} -> # Assoc was created!
{:error, changeset} -> # Handle the error
end

How Can I Associate Records From A Connected Tables? - Ruby on Rails

So Basically I Have 3 Tables that are associated together and one extra table for the table with each other: Students, Teachers, Subjects and Enrolled Subjects:
Here are my migrations:
class CreateStudents < ActiveRecord::Migration[5.0]
def up
create_table :students, :id => false do |t|
t.integer "student_id", :auto_increment => true, :primary_key => true
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
end
class CreateTeachers < ActiveRecord::Migration[5.0]
def up
create_table :teachers, :id => false do |t|
t.integer "teacher_id", :auto_increment => true, :primary_key => true
t.string "first_name"
t.string "last_name"
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
end
class CreateSubjects < ActiveRecord::Migration[5.0]
def up
create_table :subjects, :id => false do |t|
t.integer "subject_id", :auto_increment => true, :primary_key => true
t.string "subject_name"
t.timestamps
end
end
class CreateEnrolledSubjects < ActiveRecord::Migration[5.0]
def up
create_table :enrolled_subjects, :id => false do |t|
t.integer "subject_id"
t.integer "teacher_id"
t.integer "student_id"
end
end
So basically:
One students can have many subjects
One teacher can have many subjects
One students can have many teachers
So I formulated this on my models:
class Student < ApplicationRecord
has_many :enrolled_subjects
has_many :subjects, through: :enrolled_subjects
has_many :teachers, through: :enrolled_subjects
has_many :admin_users
has_secure_password
end
class Teacher < ApplicationRecord
has_many :enrolled_subjects
has_many :subjects, through: :enrolled_subjects
has_many :students, through: :enrolled_subjects
has_many :admin_users
has_secure_password
end
class Subject < ApplicationRecord
has_many :students, through: :enrolled_subjects
has_many :teachers, through: :enrolled_subjects
has_many :admin_users
end
class EnrolledSubject < ApplicationRecord
belongs_to :student
belongs_to :subject
belongs_to :teacher
end
Now to put the association on my students, teachers, subjects with my existing records I pull up my records for both on Rails Console:
students -> subjects
teachers -> subjects
students -> teachers
USING RAILS CONSOLE:
stud = Student.find(1)
subj = Subject.find(1)
stud = Student.Subject << subj
teacher = Teacher.find(1)
subj = Subject.find(1)
teacher = Teacher.Subject << subj
BUT I GOT THE FOLLOWING ERRORS:
NoMethodError: undefined method `Subject' for #<Class:0x007fcf47229980>
from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.1/lib/active_record/dynamic_matchers.rb:21:in `method_missing'
Next, I also tried to pull up the student's subjects and teachers (on my views) using the ff:
<% #students.each do |student|%>
<%= student.teachers %>
<%= student.subjects %>
<% end %>
<% #teachers.each do |teacher|%>
<%= teacher.subjects %>
<% end %>
BUT IT ONLY RETURNS ERROR:
ActiveRecord_Associations_CollectionProxy:0x007fed07a7a9a8
How can I associate the existing records on both students and tables (on Rails Console)
How can I display the records especially the Student's enrolled subjects and their teachers. And for teachers, how can I display their subjects. (via views)
First of all, I highly recommend you read this guide in its entirety. It's a thorough explanation of associations and help you understand what's going on.
But for your case, first the console. Look at what the error message is saying:
NoMethodError: undefined method `Subject' for #<Class:0x007fcf47229980>
This means there are two issues here.
First, you're finding the student and the subject properly, using their respective classes
stud = Student.find(1)
subj = Subject.find(1)
But then when you try to add a subject to a student, you go back to the class. You need to use the objects you found, which are now stored in stud and subj.
Furthermore, because your association is has_many, the method name must be plural. So the third line should look like this:
stud.subjects << subj
Second, your display. It's not returning an error, it's returning a Ruby object, which is an active record collection. So you have what you need, but now you should go a step further.
The common case is to use the each method on the collection to iterate through it and display what you'd like. For example:
<% #students.each do |student|%>
<% student.teachers.each do |teacher| %>
<%= teacher.first_name %>
<% end %>
<% end %>
But, beware of N+1 query problems. If you're going to iterate over objects like this, you should eager load the associated objects.
And one other thing, as I look through your code. Unless you have a specific and compelling reason to do so, don't change the id field. With Rails, it's best to follow the convention over configuration approach and let Rails work for you.
You should use stud.subjects in your query as student has many subjects so you have to use plural form.

How do tables join in Rails 4?

I have these models:
class A < ActiveRecord::Base
has_and_belongs_to_many :Bs
end
class B < ActiveRecord::Base
has_and_belongs_to_many :As
end
class CreateAs < ActiveRecord::Migration
def change
create_table :as do |t|
t.string :name
t.timestamps null: false
end
end
end
class CreateBs < ActiveRecord::Migration
def change
create_table :bs do |t|
t.string :name
t.timestamps null: false
end
end
end
If table 'as' has the following entries:
"A1"
"A2"
"A3"
and table 'bs' has the following entries:
"B1"
"B2"
"B3"
Does table 'as' have a foreign_key of b and vice-versa?
If yes, then how does internal mapping take place in Rails 4? How it will map? And how can I join and display both these tables?
The table does not have the foreign keys as it's a has_and_belongs_to_many relationship - they will be linked via a new join table which you need to create:
rails generate migration CreateJoinTableAB a b
You will need to create a join table for that. Something like:
class CreateJoinTableAB < ActiveRecord::Migration
def change
create_join_table :as, :bs do |t|
# add some idexes depending on your use case
# t.index :a_id
# t.index :b_id
end
end
end
Read more about this in the Rails Guide
Look at the Rails Guide for has_and_belongs_to_many. It is all described there.
With a nice picture about the database structure.
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association

Mysql2::Error: Unknown column

I work on rails 3.2.18 and mysql 5.6.20. I created migration which create new table and It looks like this:
class CreateRequest < ActiveRecord::Migration
def change
create_table :requests, id: false do |t|
t.string :id, null: false
t.integer :lesson_id, null: false
t.integer :user_id, null: false
t.boolean :approved, null: false, default: false
end
add_index :requests, :id, unique: true
end
end
I decided that I will use uuid as primary key. New record in DB I create like following:
def create_request(lesson)
request = Request.new.tap do |req|
req.id = SecureRandom.uuid
req.lesson_id = lesson.id
req.user_id = #user.id
end
request.save
end
So we have Request model and It is related to two other tables Lesson (one-to-one) ans User (one-to-many). No I will show you all models.
class PublicizeRequest < ActiveRecord::Base
self.table_name = "requests"
self.primary_key = "id"
has_one :lesson
belongs_to :user
end
class Lesson < ActiveRecord::Base
(...)
belongs_to :publicize_request
end
class User < ActiveRecord::Base
(...)
has_many :publicize_requests
end
Now I can describe main problem. If I get request from DB and I want to get user data I can do this simple:
#request.user
But If I want get lesson in the same way I get following error:
Mysql2::Error: Unknown column 'lessons.request_id' in 'where clause': SELECT `lessons`.* FROM `lessons` WHERE `lessons`.`archived` = 0 AND `lessons`.`request_id` = 'd0da41df-a9be-4575-ada0-538abe1f54a3' LIMIT 1
I am newbie in rails and I don't know how to deal with this. Thanks for all answers.
Your migration and relationship definition are not matching. The rule is that whichever table you have foreign key in, you define belongs_to relationship on that table's model.
You have defined lesson_id column in requests table, which means that Request model should have belongs_to :lesson.
class PublicizeRequest < ActiveRecord::Base
...
belongs_to :lesson
...
end
The reason you are getting the error is because you have has_one :lesson in Request model which would imply that you have belongs_to :request in your Lesson model if you have defined it, but you do not have request_id column in your lessons table.