Rails uniqueness validation does not seem to be working - ruby-on-rails

I have this validation rules below for my product model, and while testing the rules, I found that the uniqueness: true for :title actually does nothing.
validates( :title, presence: {message: ' must be given'}, uniqueness: true )
For instance, if I create two instances with same title like so,
a = Product.new title: 'title', description: 'hello!!', user: User.find(39)
a.save
id | title | description | price | created_at | updated_at | user_id |
+-----+-------+-------------+-------+--------------------+-------------+
162 | title | hello!! | 0.0 | 2018-... | 2018-02... | 39 |
b = Product.new title: 'title', description: 'hahah', user: User.find(39)
b.save
id | title | description | price | created_at | updated_at | user_id |
+-----+-------+-------------+-------+--------------------+-------------+
163 | title | hahah | 0.0 | 2018-... | 2018-02-2... | 39 |
I don't understand why the uniqueness doesn't work at all ?

Try to restart a server or reload console after adding code to any file in the project.
Uniqueness validation is not trusted in 100% events. TO be sure that some field is unique add a unique index in your database.
It's caused by that uniq validation checks that the attribute's value is unique just before save, so if two different database connections create two records with the same value it will not raise error.

Related

How to reject blank before_save callback rails?

i want to reject blank param with model callback
Schema:
Interviews
+----------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| note | varchar(50) | YES | | NULL | |
| interview_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+----------------+-------------+------+-----+---------+----------------+
Controller:
def interviews
return [] unless params[:interviews]
parse_params(:interviews).map do |interview|
Interview.find_or_initialize_by( interview }) )
end
end
Model:
class Interview < ApplicationRecord
before_save :remove_blank
def remove_blank
#new_record = false if interview_at.blank?
end
end
Example:
Input:
Interview 1( interview_at: '2019-09-15 22:00', note: 'abc')
Interview 2( interview_at: '', note: 'bcd')
Output:
Interview 1( interview_at: 2019-09-15 22:00, note: 'abc')
Interview 2( interview_at: 2019-09-15 22:00, note: 'abc')
before_save return wrong attribute when i create. How can i fix that?
Thank you for help
You can give this a try, you would want to create a validation, this will prevent the record from being saved and make the model record will return false when you call valid? on it.
class Interview < ApplicationRecord
validate :interview_at_not_blank
def interview_at_not_blank
errors.add(:interview_at, :blank, message: "cannot be blank") unless interview_at.blank?
end
end

How to using "where" to dynamic build realation

Sry for broken english
I have 2 table fruits and berries and 2 model fruit and berry, both id are primary key, berries's id is a foreign key of fruits.
The meaning is if fruit's attr is "berry" then this fruit will have hp, atk, def. other just a normal fruit, they don't have hp, atk, def.
i'm tring "where" but not work, and i have no idea to add foreign key to migrate file
it's any solutions can solve this realation
fruits
+-----+------------+-----------+
| id | name | attr |
+-----+------------+-----------+
| 123 | Blueberry | berry |
| 932 | Apple | not berry |
| 429 | Banana | not berry |
| 563 | Strawberry | berry |
+-----+------------+-----------+
berries
+-----+----+-----+-----+
| id | hp | atk | def |
+-----+----+-----+-----+
| 123 | 15 | 5 | 5 |
| 563 | 7 | 10 | 3 |
+-----+----+-----+-----+
Fruit
class Fruit < ActiveRecord::Base
has_one :berry, -> { where attr: "berry"}, foreign_key: 'id'
end
Berry
class Berry < ActiveRecord::Base
belongs_to :fruit
end
First of all bannanas are considered berries... sometimes
There are at least 2 ways of doing this
Single Table Inheritance (STI)
Multiple Table Inheritance
In STI you only create the fruits table in the database, but add all the columns the Berry class will need. Even if this method will leave many blank spaces in the DB where fruits aren't berries, I recommend it because it is pretty straight forward and supported by rails. To use it change your attr column to type and add the hp, atk and def columns in a migration:
rails g migration AddAttrsToFruit hp:integer atk:integer def:integer
rails g migration ChangeAttrToType
Since the migration generator doesn't do magic like when the migration starts with the word Change as it does with Add, you have to edit the change function in the migration it creates to look like this:
rename_column :fruits, :attr, :type
Then change your Berry class to inherit from Fruit instead of ActiveRecord::Base
class Berry < ActiveRecord::Base
belongs_to :fruit
end
Now when you create a Berry
Berry.create(name: 'Coconut', hp:100, atk:5, def:999)
Rails creates a the record in the Fruit table with all the attributes filed in:
#<ActiveRecord::Relation [#<Berry id: 1, name: nil, type: "Berry", created_at: "2015-10-14 02:38:09", updated_at: "2015-10-14 02:38:09", hp: 1, atk: nil, def: nil>]>
For MTI you can read the link.
Good luck :)
Great answer from robertoplancarte - to explain a little more simply for you, you're looking to use a has_many/belongs_to relationship:
#app/models/fruit.rb
class Fruit < ActiveRecord::Base
has_many :berries
end
#app/models/berry.rb
class Berry < ActiveRecord::Base
belongs_to :fruit
end
You can set it up in your database as follows:
#fruits
+-----+------------+-----------+
| id | name | attr |
+-----+------------+-----------+
| 123 | Blueberry | berry |
| 932 | Apple | not berry |
| 429 | Banana | not berry |
| 563 | Strawberry | berry |
+-----+------------+-----------+
#berries
+-----+----------+----+-----+-----+
| id | fruit_id | hp | atk | def |
+-----+----------+----+-----+-----+
| 1 | 123 | 15 | 5 | 5 |
| 2 | 932 | 10 | 3 | x |
+-----+----+-----+----+-----+-----+
This will allow you to call...
#fruit = Fruit.find params[:id]
#fruit.berries
What robertoplancarte was saying was your current setup is pretty weak:
You're identifying which "fruit" is a berry manually
You're then populating another model with data which could be put into the first
The way around this is to use something called an STI - Single Table Inheritance.
This is a Railsy way to use a single model to define multiple types of data:
#app/models/fruit.rb
class Fruit < ActiveRecord::Base
#columns id | type | name | hp | atk | def | created_at | updated_at
end
#app/models/berry.rb
class Berry < Fruit
end
This will give you the ability to call:
#berry = Berry.find x
This is more appropriate for your requirements; is somewhat advanced, but nothing a question on StackOverflow would be defeated by.

Rendering a JSON object of a join-model and its associated models

In a Rails ( 4.1.5 / ruby 2.0.0p481 / win64 ) application I have a many-to-many relationship between Student and Course and a join model StudentCourse which represents the association, and has an additional attribute called started (set by default on "false").
I also have added an index in the join-table made of student_id and course_id, and set a unique check on that, like this
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
I wanted that to be a composite primary key, but since in rails there are no composite primary keys (without using a gem) I also added a primary key called id:
t.column :id, :primary_key
Now I see that associations are created by either doing:
Student.first.courses.create(:name => "english")
or
Course.first.students << Student.first
This is fine and it's the expected behaviour, I suppose.
That said, I am struggling to wrap my mind around association resolutions in ActiveRecord queries. Let me explain this better:
For reference, Student.all, Course.all and StudentCourses.all would return tables like these:
Student.all
+----+-----------+
| id | name |
+----+-----------+
| 1 | Aidan |
| 2 | Alison |
| 3 | Elizabeth |
+----+-----------+
Course.all
+----+----------+------------------+
| id | name | description |
+----+----------+------------------+
| 1 | english | desc. here |
| 2 | music | desc. here |
| 3 | dance | desc. here |
| 4 | science | desc. here |
| 5 | french | desc. here |
| 6 | arts | desc. here |
+----+----------+------------------+
StudentCourse.all
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 1 | 1 | false | 1 |
| 2 | 1 | false | 2 |
| 3 | 1 | false | 3 |
| 1 | 2 | true | 4 |
| 2 | 2 | true | 5 |
| 4 | 3 | false | 6 |
| 5 | 2 | false | 7 |
| 5 | 1 | true | 8 |
| 6 | 2 | false | 9 |
+-------------+------------+------------+----+
So far I can happily render a json object of all courses, and names of all students for each course like this:
render json: Course.all.to_json(:include => {:students => {:only => :name}})
I can also easily render all courses that a student is attending or about to attend with
render json: #student.courses.to_json(:include => {:students => {:only => :name}})
which also includes other students for those courses.
But suppose I wanted to render one student's courses which the student has not yet started, together with all the other students who are on that course (which have or not started the course) [Please read the UPDATE section below, I'm looking for the opposite thing actually!]
I think the easieast approach is to query the join-table for something like:
StudentCourse.all.where(student_id: #student.id, started: false)
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 5 | 2 | false | 7 |
| 6 | 2 | false | 9 |
+-------------+------------+------------+----+
But how do I go on from this resulting table (association object) to get a nicely packaged json object with courses names (and all other attributes) and also including students in it, like I did with: Course.all.to_json(:include => {:students => {:only => :name}}) ?
I think I'm missing some basic knowledge of some important key concepts here, but at this point I cannot even indentify them and would greatly appreciate some help... thank you.
Update:
I just realized that the following part is what I was originally trying to do. It's the opposite thing. Among all these details I got lost along the path. I hope that it's ok if I just add it here.
So, given a student (let's call him Aiden), I need to return a JSON object containing only the courses that he is in and that he has started, only when such courses have other students in them who have not started them, and it has to include the names of those students for each course too.
So...
I now have:
aiden_started_courses = Student(1).courses.where(:student_courses => {:started => true } )
which for a student takes all the courses that have a "true" value in the join-table "started" column. (again in the join table each student-course record is "compositely" unique, so there can just be one unique record for a given student_id and course_id).
With the next query, for one of "aiden_started_courses" I can pull off all the relative student-courses associations which have a false value on "started"
aiden_started_courses[0].student_courses.where(:started => false).includes(:student).to_json(:include => :student)
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 1 | 2 | false | 4 |
+-------------+------------+------------+----+
| 1 | 9 | false | 5 |
+-------------+------------+------------+----+
So here lies the problem: I have managed to get this just for a single course in aiden_started_courses array, but how would I be able to build a query that returns this data for all of Aiden's started courses?
Is it possible to do that in one line? I know I could probably use Ruby enumerator loops but I somewhat feel that I would be kind of breaking some pattern both on a Rails coding convention level and on performance level? (hitting N+1 problem again?) ...
What I could so far:
I came up with this where I find all students who have not started the courses which a given user has started:
Student.includes(:student_courses).
where(:student_courses => { :started => false, :course_id => aiden.courses.where
(:student_courses => {started: true}).ids } )
or this:
Course.includes(:students).where(:student_courses => {:started => false,
:course_id => aiden.courses.where(:student_courses => {:started =>true}).ids })
which finds all the courses that a given student has started if those courses include students who have not started them yet
But what I really need is to get a JSON object like this:
[
{
"id": 1,
"name": "english",
"students": [
{"name": "ALison"},
{"name": "Robert"}]
},
{
"id": 2,
"name": "music",
"description": null,
"students": [
{"name": "Robert"},
{"name": "Kate"}]
}
]
where I can see the courses that a given student is on and has started, but only those in which there are other students that have not yet started it, together with the names of those students...
I'm thinking that probably there is no way how I could get that through a regular AR query, so maybe should a build a JSON manually? But how could I do that?
Thanks in adv. and I apologise for the verbosity.. but hopefully it will help..
Use scope to your advantage:
class Course < ActiveRecord::Base
# ...
scope :not_started, -> { joins(:student_courses) \
.where(student_courses: {started: false}) }
scope :with_classmates, -> { includes(:students) } # use eager loading
end
Then call:
#student.courses.not_started.with_classmates \
.to_json(include: {students: {only: :name}})
Output:
[
{
"id": 1,
"name": "english",
"description": null,
"students": [
{"name": "Aiden"},
{"name": "Alison"}]},
{
"id": 2,
"name": "music",
"description": null,
"students": [
{"name": "Aiden"},
{"name": "Alison"}]},
{
"id": 3,
"name": "dance",
"description": null,
"students": [
{"name": "Aiden"}]}]
Use JBuilder, it comes by default with Rails. Ignore the lines starting with '#':
Jbuilder.new do |j|
# courses: [{
j.courses <student.courses - replace this with whatever variable> do |course|
# id: <course.id>
j.id course.id
# name: <course.name>
j.name course.name
# students: [{
j.students <course.students - replace with whatever variable> do |student|
# name: <student.name>
j.name student.name
end
# }]
end
# }]
end
Not a lot of code. Removing the comments and simplifying some features, it will look like:
student_courses = <...blah...>
json = Jbuilder.new do |j|
j.courses student_courses do |course|
j.(course, :id, :name)
j.students <course.students - whatever method here>, :name
end
end.target!
Check out their guide, its pretty awesome to generate JSON in plain Ruby DSL. So go ahead and use whatever ruby code you want to fetch students/courses/studentcourses.

AwesomeNestedSet and sortable tree

In default AwesomeNestedSet gem is sorting by :lft attribute. Suppose I have a class:
class Category < ActiveRecord::Base
acts_as_nested_set
attr_accessible :name, :position, :parent_id, :lft, :rgt
end
How can I create a sortable (by :position attribute) tree with AwesomeNestedSet gem with one hit to the database where :position is used for sorting siblings (level)?
I need output something like this:
----------------------------------
id |position | name | parent_id |
----------------------------------
1 | 1 | item1 | nil |
----------------------------------
2 | 1 | item11 | 1 |
----------------------------------
3 | 1 | item111| 2 |
----------------------------------
4 | 2 | item12 | 1 |
----------------------------------
5 | 2 | item2 | nil |
----------------------------------

Is there an activerecord relationship to solve this problem?

I can't seem to wrap my head around this. I have three tables:
mysql> desc users;
+----------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | YES | | NULL | |
+----------------------+--------------+------+-----+---------+----------------+
mysql> desc mentions;
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| message_id | int(11) | YES | | NULL | |
| mentionable_type | varchar(255) | YES | | NULL | |
| mentionable_id | int(11) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
mysql> desc messages;
+------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| body | text | YES | | NULL | |
| user_id | int(11) | YES | | NULL | |
+------------+----------+------+-----+---------+----------------+
And the following relationships:
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
has_many :mentions
end
class Mention < ActiveRecord::Base
belongs_to :mentionable, :polymorphic => true
belongs_to :message
end
I'm not sure if I'm using it correctly, but I used the polymorphic relationship in Mention because mentionable_type could be 'User' or 'Group'. I've left the Group stuff out of this post as it's not related to this question.
When a user creates a Message, their user_id is stored in the messages table. I can easily return a list of a user's "authored" messages with:
current_user.messages
Similar to a tweet, the message's body may, or may not, contain mentions of n users or groups. When the message "I'm having lunch with #userA, #userB, and #groupX." is created, the body would be parsed and those three "mentions" would be created as well.
I can easily return all of a user's "mentions" with:
current_user.mentions
If I want to see the message of a mention, I can do:
mention = current_user.mentions.first
mention.message
What I can't seeem to figure out is a clean way to combine the two and get a list of messages that a user created AND were mentioned in. Any ideas?
I your User model, this line should be present for polymorphic relationships.
class User
has_many :messages
has_many :mentions, :as => :mentionable
end
And try this:
user_id = 10
#messages = Message.find(:all, :joins => [:mentions],
:conditions => ['messages.user_id = ?', user_id])

Resources