Rails: id field is nil when calling Model.new - ruby-on-rails

I am a little confused about the auto-increment id field in rails. I have a rails project with a simple schema. When i check the development.sqlite3 I can see that all of my tables have an id field with auto increment.
CREATE TABLE "messages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "text" text, "created_at" datetime, "updated_at" datetime);
But when I call Message.new in the console, the resulting object has an id of nil.
>> a = Message.new
=> #<Message id: nil, text: nil, created_at: nil, updated_at: nil>
Shouldn't the id come back populated?

No, that's the correct behavior. When you create an object via new (as in your example), Rails doesn't persist it to the database (just in memory).
If you do Message.create, or Message.save like theIV said, then the id will be populated.

Like fig said,
n = Movie.new
n.save
=> true
means that it is saved and will be given an ID. Alternatively,
n = Movie.create!
automatically saves and stores it in the database and gives it an ID with one line of code.

As far as I know, the id field only gets assigned on saves, not on news.

All answers above are correct. To add to them, calling the #save method on an instance will return true if it was able to insert into database and false if it wasn't successful. Using the ::create method will make a new instance of your model, and will call #save on it.
You can also use the #save! or ::create! methods. The difference is that these will raise an error (as opposed to returning boolean) if they were not successful in their insertion to the database.

In my case, I called Model.create(event_id) with constant value (6) that was NOT the event's actual id. There was no error, and an Active Record Model was returned with other attributes, except id was nil.
I fixed my bug after some hours of debugging by changing the value to the actual reference id of the existing Event.id (as it was previously). Note that you might get this error if you have SQL foreign constraint to some other table, but you are using incorrect id for the reference.

Related

Updating if exist or create if not rails

So im using an api to get info on weather, its executes everyday, what im trying to do is to get updated if already exist, and create a new one if it doesn't in table.
I do want to update all attributs when udpdating.
i did try
model = Model.where(column_name: value).first_or_initialize(locked: false)
but i get an error saying :
unknown attribute locked for Model
raise UnknownAttributeError.new(self ,k.to_s)
If you need anything, ask and i will comment or edit. Im newb to ruby and rails
Firstly, the model.Model part should be just Model, as Model is your class.
locked is supposed to be a column/attribute of the Model class, although it seems is not the case judging from your error. Therefore, I'm gonna use other_column_name as an example.
Explanation of what this is doing:
Model.where(column_name: value).first_or_initialize(other_column_name: some_value)
Model.where(column_name: value): gets models that satisfy the condition column_name == value
first_or_initialize: if a model such that column_name == value was found, that one is returned. Otherwise, it initializes a model with column_name = value.
By passing other_column_name: some_value, if the model was not found and needs to be initialized, it sets other_column_name to some_value but: 1) it does not update it if it was initially found and 2) it does not save the record.
The equivalent of first_or_initialize that saves the new record would be first_or_create but this would still not update the record if it already existed.
So, you should do something like this:
m = Model.where(column_name: value).first_or_initialize
m.other_column_name = some_value
m.save
This way, you first get a model where column_name is value or initialize a new one with this value if it didn't already exist. Then, you set the attribute other_column_name to some_value and save the model.
A one-liner alternative would be
Model.where(column_name: value).first_or_create.update(other_column_name: some_value)
However, note that if it needs to be created, this one will perform 2 queries (the insert and the update).
About the error part. It says the attribute locked does not exist on the Model record. Are these classes you created? Are you using some pre-existing project? You could try posting Model.attribute_names and maybe your schema.rb
Firstly refer to the docs here
A table by the name of weather with the following attributes location: string temperature:integer wind:string needing to be updated or initialized based on the location would work like this
#weather_record = Weather.find_or_initialize_by(location: location_value)
#weather.temperature = -60
#weather.wind = strong
#weather.save
Next, never, ever use a reserved name for a model so do not have Model as the name of your table
Lastly in your example
model.Model.where(column_name: value).first_or_initialize(locked: false)
you are saying
a_record.ClassName.where which is just wrong, If you are using a class method then start with the class name e.g. Weather.where if you are using instance methods then use the instance name e.g. an_instance_of_weather.some_field
Try this mate:
column_name_value = (Way that you get the info from data)
model = Model.find_or_initialize_by column_name: column_name_value
Let me know if worked!

Has self.column = value to update been deprecated in Rails?

I was just trying to use
self.name = 'Tom'
inside my user model but the column does not get updated.
Using
self.update_column(:name, 'Tom')
and
update_column(:name, 'Tom')
works.
Has the first way of changing value been deprecated?
self is an object, when you do self.name = 'Tom', it just sets the new value in attribute at object level, but to save it in the database, you need to call save method on self after setting the value.
self.name = 'Tom'
self.save!
Give it a try!
self.name = 'Tom'
Calls the setter method name= that ActiveRecord generates from your columns. This just updates the object in memory and marks the attribute as changed so when you call .save or .save! the changes are persisted to the database.
Has the first way of changing value been deprecated?
No. You just completely misunderstood what it does. Using the setters has never automatically caused a database update. That would be really unexpected behavior.
self.update_column(:name, 'Tom') and update_column(:name, 'Tom') are actually the exact same thing. The first just has an explicit recipient while the later is implicit. But in both cases the recipient is self. Like its big brother the #update method this creates a UPDATE sql query.

rails object created from method or services class is not automatically initialised

I am relatively new to Rails and trying to create an object from a model (I've also tried the same from a service I created in app/services/).
The object gets created, but fails when I access the page that tries to call a method on the object (I want to use .find(id) and I get a message about 'nil' method, because of this):
When I check the object in the console after the error of a nil method, the object has been created, but all the variable types are set to 'nil':
If I create a new object of the same class from the console, the object has variable that are typed:
Trans(id: integer, transaction_id: integer, sender_id: string, recipient_id: string, created_at: datetime, updated_at: datetime, amount: float)
The code that creates the object is
transaction = Trans.new
transaction.find(id)
I'm calling this from within a method inside the model. I've tried the same by putting everything in a service and create a service object from within it. The result is the same.
Does this mean that the constructor/initialiser is not being called for some reason? I've not overwritten or changed the initialiser.
This is with Rails 4.
I'm trying to create an object so I can query the database.
Although I've been working several hours on this, I figured out what was wrong shortly after I posted my question.
The object needs to be created like this:
trans = Trans.find(id)
instead of
trans = Trans.new
result = trans.find(id)
This seems counter-intuitive to me, but it works.
I hope this saves someone the several hours I lost!

rails & postgreSQL - How can I update a data with existing primary key?

I want to update some data in Lecture(id, name, etc.) table.
For example, there is a data(id: 1, name: "first") in the Lecture.
When I typed Lecture.create(id: 1, name: "newer") =>
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "lectures_pkey"
Is there any way to update data?
Try this:
Lecture.find(1).update(name: "newer")
Find more information on update here: http://guides.rubyonrails.org/active_record_basics.html#update
The reason it didn't work is because the id is unique. When you were using create, it was trying to create a NEW record with id of 1, not edit the same record.
PG::UniqueViolation: ERROR: duplicate key value violates unique
constraint "lectures_pkey"
id is the default primary key which shouldn't be changed or duplicated. As you are inserting a row with an existing id, you get that error.
Instead, you need to do like below.
#lecture = Lecture.find(1)
#lecture.update_attributes(name: "newer")
You can use
lec = Lecture.find(1)
lec.update_attributes(name: "newer")
You are getting error PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "lectures_pkey" due to you can not create record with same id. Id is primary key in table.
All of the current answers are correct if you don't mind loading the record and then updating it. If you don't need to actually load the record, and just want to update data in the database you can do
Lecture.where(id: 1).update_all(name: "newer")
This will skip all validations, etc and just do a direct sql update of the data in the database of the record with id == 1

Update association by ID other than primary key

Let's say I want to update an association by passing in a key other than the primary key, like social security number:
Teacher.find(params[:id]).update({'student_ids'=>['123-45-6789','987-65-4321']})
How can I make my Teacher model understand that it will be receiving SSN, and not database IDs? A SSN uniquely identifies a student.
Edit: I like Pavling's solution, as it keeps the logic in the model. However this fails:
Teacher.new({name:'Miss Molly',student_ssns:['123-45-6789','987-65-4321']})
With ActiveRecord::UnknownAttributeError: unknown attribute: student_ssns
How about...
student_ids = Student.where(ssn:['123-45-6789','987.65.4321']).map(&:id)
Teacher.find(params[:id]).update({'student_ids'=> student_ids})
If SSN is a unique identifier for your student rows, you could make it the primary key instead of the default integer - but convention would advise to leave the 'normal' ID field there.
So how about a method on Teacher which takes student_ssns rather than student_ids, and inside that method, find the student ids (similar to Kevin Monk's answer)?
# teacher.rb
def student_ssns=(ssns)
student_ids = Student.where(ssn: ssns).pluck(:id)
update({student_ids: student_ids})
end
Then you can use it thus:
Teacher.find(params[:id]).student_ssns(['123-45-6789','987.65.4321'])
Using Pavling's solution, but changing the definition line to:
def student_ssns=(ssns)
should get it working properly for you.

Resources