Does Rails has a way to validate if the foreign key value exists in an optional relationship? - ruby-on-rails-5.1

I am using Rails version 5.1.3.
Does Rails have a way to validate if the foreign key value exists in an optional relationship?
I have the next model:
class Post < ApplicationRecord
belongs_to :category, optional: true
validates :category, presence: true, allow_nil: true
end
This is the migration:
class CreatePosts < ActiveRecord::Migration[5.1]
def change
create_table :posts do |t|
t.string :name, limit: 100
t.references :category, foreign_key: true, null: true
t.timestamps
end
end
end
These are the cases:
# Case 1
p1 = Post.new({})
p1.save #Working as I expected ... record Inserted
# Case 2
p2 = Post.new({category_id: 3}) # A category with id 3 exists
p2.save #Working as I expected ... record Inserted
# Case 3
p3 = Post.new({category_id: 30}) # A category with id 30 does not exists.
p3.save # Not working as I expected
In the case 3 I was expecting an Active Record validation error something like This Category does not exists but instead of I get
an SQL message:
INSERT INTO "posts" ("category_id", "created_at", "updated_at") VALUES (?, ?, ?) [["category_id", 10], ["created_at", "2017-09-26 00:59:47.645185"], ["updated_at", "2017-09-26 00:59:47.645185"]]
ActiveRecord::InvalidForeignKey: SQLite3::ConstraintException: FOREIGN KEY constraint failed: INSERT INTO "posts" ("category_id", "created_at", "updated_at") VALUES (?, ?, ?)

This could help:
validates :category,
presence: true,
if: -> { category_id.present? }

The database is enforcing foreign key integrity, which just means that it will not allow you to associate things to another thing that does not exist. This is expected behavior.
If you want to do something if the association doesn't exist, you should do a find first before attempting to create the association.
The optional validation you set up for the foreign key relationship just means that a relationship is not required. In other words, you can create posts that are not associated with categories. However, that does not mean the database will be happy if you attempt to form an association with categories that do not exist. Rails is cool whether or not you provide an association or not, but the moment it attempts to do something that violates rules set by the database, then the database has every right to complain!

Related

Ecto delete many_to_many join whilst keeping joined records in tact

I have two Ecto models: User and Skill, which are joined with a many_to_many association via a users_skills table:
create table(:users_skills, primary_key: false) do
add :user_id, references(:users, on_delete: :nothing)
add :skill_id, references(:skills, on_delete: :nothing)
end
create unique_index(:users_skills, [:user_id, :skill_id])
In the User schema there is:
many_to_many :skills, Skill, join_through: "users_skills"
And in the Skill schema there is:
many_to_many :users, User, join_through: "users_skills"
What I want to do is delete a user’s skills without deleting the User or Skill itself. Currently I’m trying this:
query = Ecto.assoc(current_user, :skills)
Repo.delete_all(query)
However it throws the error:
(foreign_key_violation) update or delete on table "skills" violates foreign key constraint "users_skills_skill_id_fkey" on table "users_skills"
When adding on_delete: :delete_all to the users_skills migration, this has the undesired effect of deleting the associated skills.
How do I approach this so that only the association record is deleted with both User and Skill staying in tact?
I'm sure there're better ways to do this but you can pass on_replace: :delete option to many_to_many macro in your schemas.
defmodule User do
use Ecto.Schema
schema "users" do
field(:name)
many_to_many(:skills, Skill, join_through: "users_skills", on_replace: :delete)
timestamps()
end
end
Now if you run
current_user
|> Repo.preload(:skills)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:skills, [])
|> Repo.update!()
It deletes from users_skills table and skills table will remain intact.

When to seperate database column meaning (generic columns)

I'm designing a feedback system for a site which has approximately 20 questions, but those questions may change each year and trying to consider the best design.
The simplest way is to map each question into a column, but this would require creating a new table each year and changing the application code which isn't sensible at all.
The second option is to separate the columns from the meaning and have a second table that applies meaning, i.e.
Table1: (row per survey) Table2: (row per questionnaire type)
QuestionID AnswerID
Question1 QuestionID
Question2 Answer1
... ....
Question20 Answer20
The third option I can think of is to completely separate each aspect like:
Table1: Table2: Table3: Table4:
QuestionID AnswerID MatchTableID SetID
QuestionValue AnswerValue QuestionID FeedbackSet
AnswerID QuestionID
Which gives it the benefit of scalability, but it may be excessive for something changing at most once a year for a few hundred records and i'm not sure if that doesn't feel a bit too much like an Entity-Attribute-Value design.
I'd appreciate any comments on what is considered to be best practice here and what is considered acceptable practice.
You might want to just keep it simple and go with a classic relational setup.
The models:
class Question < ActiveRecord::Base
has_many :replies
has_many :answers
belongs_to :questionnaire
end
class Answer < ActiveRecord::Base
belongs_to :question
has_many :replies
end
# A collection of questions
class Questionnaire < ActiveRecord::Base
has_many :questions
has_many :replies, through: :questions
end
# A user reply to a question
# acts as a many to many join table
class Reply < ActiveRecord::Base
belongs_to :question
belongs_to :answer
end
The schema:
ActiveRecord::Schema.define(version: 20160215124045) do
create_table "answers", force: :cascade do |t|
t.text "text"
t.integer "question_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "answers", ["question_id"], name: "index_answers_on_question_id"
create_table "questionnaires", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "questions", force: :cascade do |t|
t.text "text"
t.integer "questionnaire_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "questions", ["questionnaire_id"], name: "index_questions_on_questionnaire_id"
create_table "replies", force: :cascade do |t|
t.integer "question_id"
t.integer "answer_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "replies", ["answer_id"], name: "index_replies_on_answer_id"
add_index "replies", ["question_id"], name: "index_replies_on_question_id"
end
Yeah its a lot of tables - but this gives you a great deal of flexibility without dealing with the hassle that is key value tables. And its also much faster to build than some hacky attempt at a dynamic schema.
Its also really easy to pull out metrics if needed.
This example makes quite a few assumptions:
The questions are multiple choice - not write in. You can quite simply adapt it by storing the write-ins on the replies table.
The relation between question and questionnaire is one-to-one. Use a join table and HABTM relationship or has_many through: if need to be able to reuse questions on multiple questionnaires.

Is it possible to display data through a id?

My apologies for the abstract title. I'm in the proces of adding a friendship feature in my Rails/Angular application following #163 Self-Referential Association.
With the help of some other users here I've created the option to add friends, but I'm having some trouble displaying the correct data (name, etc) in a friendslist.
I have a friendships table,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
When a friend is added it creates a new record in the table with a user id and a friend id.
{"id":1,"user_id":1,"friend_id":2,"created_at":"2015-12-29T13:18:44.499Z","updated_at":"2015-12-29T13:18:44.499Z"},
{"id":2,"user_id":1,"friend_id":3,"created_at":"2015-12-29T13:18:45.463Z","updated_at":"2015-12-29T13:18:45.463Z"},
{"id":3,"user_id":1,"friend_id":4,"created_at":"2015-12-29T13:18:46.420Z","updated_at":"2015-12-29T13:18:46.420Z"}
In the friendships_controller.rb I have,
def index
friend = current_user.friendships
render :json => friend.to_json()
end
So I can see what friend_id is connected to a user_id. But if I want to display the friends in a ng-repeat I'm going to need more data.
The friend_id is the added users id. So if a user adds the 4th user to his friendslist the friend_id would be 4. So it possible to connect that friend_id to the user_id so I can then show the {{ user.name }} or do I need to add all the data like name, birthday etc to the parameters of the friend create function?
So for example I would add the friends name to the parameters so the JSON looks like this,
{"id":1,"user_id":1,"friend_id":2,"name":john,"created_at":"2015-12-29T13:18:44.499Z","updated_at":"2015-12-29T13:18:44.499Z"},
{"id":2,"user_id":1,"friend_id":3,"name":david,"created_at":"2015-12-29T13:18:45.463Z","updated_at":"2015-12-29T13:18:45.463Z"},
{"id":3,"user_id":1,"friend_id":4,"name":cash,"created_at":"2015-12-29T13:18:46.420Z","updated_at":"2015-12-29T13:18:46.420Z"}
Then I can do something like ng-repeat friend in friendships {{ friend.name }}

Will simplify inserts into tables with auto-generated primary keys?

In Slick 1.x, inserting into a table with an auto generated primary key was kind of complicated: you had to manually create a table projection that omitted the pk for insert purposes. It looks like Slick 2.x will fix this problem:
Soft inserts are now the default, i.e. AutoInc columns are automatically skipped when inserting with +=, ++=, insert and insertAll. This means that you no longer need separate projections (without the primary key) for inserts.
However the 2.x docs must not be updated:
While some database systems allow inserting proper values into AutoInc columns or inserting None to get a created value, most databases forbid this behaviour, so you have to make sure to omit these columns. Slick does not yet have a feature to do this automatically but it is planned for a future release. For now, you have to use a query with a custom projection which does not include the AutoInc column
Does anyone know the new 2.0 syntax for doing an insert into a table with AutoInc and get the generated key back?
The syntax for inserts is the same as in 1.0, only that now autoinc columns are automatically ignored. So there is a semantic change in what .insert does. If you want the old behavior (where they are included) you have to call .forceInsert.
You can retrieve the generated value like this:
case class Employee( empName: String,empType: String, empId: Int = 0)
class Employees(tag: Tag) extends Table[Employee](tag, "emp") {
def empId = column[Int]("id", O.PrimaryKey, O.AutoInc)
def empName = column[String]("name", O DBType ("VARCHAR(100)"))
def empType = column[String]("type")
def * = (empName, empType, empId) <> (Employee.tupled, Employee.unapply)
}
val employees = TableQuery[Employees]
val myInsert = employees.map(e => (e.empName, e.empType)) returning employees.map(_.empId)
val autoGenratedKey = myInsert.insert("satendra", "permanent")

How to make column data unique for each user_id in ruby on rails

The problem is that I have a table customers with some customers related columns like customersID.
Also I have a column user_id So that the customers data only relate to one user.
class Customers
belongs_to :user
end
class Users
has_many :customers
end
Now I have the :unique on the customersID. But this makes every customerID unique all over the table.
What i want is that the customerID is unique per user_id.
Any idea or suggestions?
Edit: Question seems bit unclear.
I have a table users
user1
user2
user3
also i have a table customers where each customer get a user_id from the user who created him. The user can input a customerID, which should be unique for each user.
customerID=1 user_id1
customerID=2 user_id1
customerID=1 user_id3
customerID=3 user_id1
customerID=1 user_id2
...
I crud the customers data via #customers = current_user.customers in my CustomersController. The customerID is a simple t.integer "customerID"
It seems as if you want a Customer to have a User, is that correct? Try something like this:
class Customer
has_one :user
end
class User
belongs_to :customer
end
That way you can have a customer tied to one specific user_id. Then you could do something like this:
#customer = Customer.where(name: "company name").first
#user = #customer.user #this will find the customer's unique user
Why not force uniqueness of the pair Customer_id, iser_id via an index on your Customers table ? You could create a migration something like
add_index_to_costumers.rb
class AddIndexToCustomers < ActiveRecord::Migration
def change
add_index :customers, [:customerID,:user_id], unique: true
end
end
It should ensure at the database level that the pair customerID/user_id is unique. If I well understand your question it's what you expect.
Hope this helps
Cheers

Resources