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

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

Related

Search In a column which stores an array

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

Delete only join record between tables in Rails 4

I have 3 Rails models as:
class User < ActiveRecord::Base
has_and_belongs_to_many :char_factors
end
class CharFactor < ActiveRecord::Base
has_and_belongs_to_many :users
end
class UserCharFact < ActiveRecord::Base
belongs_to :user
belongs_to :char_factor
end
In the above User and CharFactor models are joined through the UserCharFact model.
I'm creating new relations as:
def create
#user_character = UserCharFact.create({:user_id => #user.id, :char_factor_id => factor_id.id})
end
And the above seems to be working properly. But I can't find a way to delete a specific join relation between 2 tables. I tried the following:
def destroy
#user_character = CharFactor.find(params[:id])
#user.char_factors.delete(#user_character)
end
But it actually deletes the value from CharFactor table rather than just deleting the association
UserCharFact.where(char_factor_id: params[:id], user_id: #user.id).destroy_all
You delete it just like you delete any other model records.
user_char_factor = UserCharFactor.find_by(user_id: user_id, char_factor_id: char_factor_id)
user_char_factor.destroy if user_char_factor

Updating the values on Many-to-Many relationship table

I'm facing an issue on the many to many relationship table using ruby on rails.
I have two tables and a relationship table(which is a joining table). They are named as follow
User
Role
Role_User
My problem is how do i update the values of user roles on the 3rd table.
Example
If I assign a
user_id 34 to admin(role_id = 2)
user_id 34 to supervisor(role_id = 3)
I should have two entries on Role_User table. Now I would like to update user_id 34 to admin(role_id = 1) How do I do this on rails
I have updating the user details with update_all command on rails like below
#user = User.where(id: user_params[:id]);
#user.update_all(
first_name: user_params[:first_name],
last_name: user_params[:last_name],
email: user_params[:email],
contact_number: user_params[:contact_number],
user_name: user_params[:user_name],
password: user_params[:password]
);
I try to update the role of users using the following code but it fails
#roles = user_params[:roles];
for role in #roles
#user.roles << Role.find(role)
end
#user.save;
You should be able to do something like that:
role = Role.find(ID)
user.roles << role
user.save
If you set up a many_to_many connection through rails, like below for example, then you don't have to touch the relational table. As nifCody said, you simply have to tell rails that you're appending to the user's roles and (as long as you have the proper associations) rails will automatically update the middle table for you.
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
end
class UserRole < ApplicationRecord
belongs_to :user
belongs_to :role
end
class Role < ApplicationRecord
has_many :user_roles
has_many :users, through: :user_roles
end
But essentially, whatever is in the table is what rails sees. So if you already have data in the tables, you can leave it and continue with the method nifCody suggested. It is simply a different way of modifying the data not accessing it.
I have found a way to doing the updates on the third relationship table as following code,
First I delete all role_id for the particular user_id
Then insert the roles as new to the particular user_id
Here is the code I have written
#user = User.find(user_params[:id])
#roles = #user.roles.all
#roles = #user.roles.all
for role in #roles
if role
#user.roles.delete(role)
end
end
#user = User.find(user_params[:id])
#roles = user_params[:roles];
for role in #roles
#user.roles << Role.find(role)
end
if #roles
#user.save
end

How are I18n and database ActiveRecord querying related?

Does anyone know, what does I18n have to do with database?
class DecorativeCentersSalesRepresentative < ActiveRecord::Base
belongs_to :decorative_center, class_name: ::DecorativeCenter
belongs_to :user, class_name: ::SalesRepresentative
end
class DecorativeCenter < ActiveRecord::Base
has_many :decorative_centers_sales_representative
has_many :sales_representatives,
through: :decorative_centers_sales_representative
end
class SalesRepresentative < User
has_many :decorative_centers_sales_representative,
foreign_key: :user_id
has_many :decorative_centers,
through: :decorative_centers_sales_representative,
foreign_key: :user_id
end
All is good, and I can do
SalesRepresentative.last.decorative_centers
SalesRepresentative Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`type` IN ('SalesRepresentative') ORDER BY `users`.`id` DESC LIMIT 1
DecorativeCenter Load (0.3ms) SELECT `decorative_centers`.* FROM `decorative_centers` INNER JOIN `decorative_centers_sales_representative` ON `decorative_centers`.`id` = `decorative_centers_sales_representative`.`decorative_center_id` WHERE `decorative_centers_sales_representative`.`user_id` = 4
#=> [#<DecorativeCenter:0x000000088e5578]
But when I do
DecorativeCenter.last.sales_representatives
DecorativeCenter Load (0.2ms) SELECT `decorative_centers`.* FROM `decorative_centers` ORDER BY `decorative_centers`.`id` DESC LIMIT 1
#=> I18n::InvalidLocale: :en is not a valid locale
#=> from /home/andreydeineko/.rvm/gems/ruby-2.3.0#profill-base/gems/i18n-0.7.0/lib/i18n.rb:284:in `enforce_available_locales!'
WHY??
I know it is an invalid locale, valid one is :pl:
I18n.available_locales
#=> [:pl]
I18n.default_locale
#=> :pl
But how are these things even related and why can I query one way, and can not other?
After some time of debugging I have found the real issue.
Starting from the end:
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(reflection = nil)
if reflection
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
source_associations = reflection.through_reflection.klass._reflections.keys
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
else
super("Could not find the source association(s).")
end
end
end
In this error locales were hardcoded, and it is :en, that is why I could not even get the error message.
1) In my app :en was not within available locales, so in order to get the error message Rails was tying to spit out, I temporarily set the app's locale to :en.
2) Now I could get the error:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) "sales_representative" or :sales_representatives in model DecorativeCentersSalesRepresentatives. Try 'has_many :sales_representatives, :through => :decorative_centers_sales_representatives, :source => <name>'. Is it one of versions, decorative_center, or user?
Which simply states, that I was wrong in writing belongs_to in my join table.
AR expects the name of an association to be defined, not a table in database.
So changing
# (STI) table users, but AR model is called SalesRepresentative
belongs_to :user, class_name: ::SalesRepresentative
to
# changed to real AR table name passing the foreign_key
belongs_to :sales_representative, class_name: ::SalesRepresentative, foreign_key: :user_id
made it work as expected.

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.