Referencing a newly created ActiveStorage::Attachment - ruby-on-rails

Is there a way to create and return the attachment in one method call?
#message.images.attach(params[:images]) # returns true or false
#message.images.last # returns last attachment, which may not be the same one

Related

How to check remove image? I can not check because before save averter.present? return true

I use carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks.
#foo.assign_attributes(update_params) #=> update_params includes "remove_avatar"=>"1"
#foo.avatar.present? #=> true
#foo.save
#foo.avatar.present? #=> false
before save present? return true. after save present? return false.
My Validation
validate :validate_avatar
def validate_avatar
# if #foo.avatar deleted and same conditions, add error
end
In validate_avatar, #foo.avatar.present? is true because before save.
I try after_save :validate_avatar.
after_save :validate_avatar
def validate_avatar
# if #foo.avatar deleted and same conditions, add error
# then return false
end
Because after_save, #foo.avatar.present? is false. But Rails5 not rollback with return false.
How to check #foo.avatar deleted in validation?
I want to check avatar was deleted but remain image crop data. So I need check #foo.avater deleted or not delete.
If avatar deleted, image crop data deleted too. So check in validation.
Already made a method for delete all image crop data(xxx_crop columns) in my project.
In your controller you can do
if #foo.save
<do whatever>
else
render json: #foo.errors
so #foo will have an errors attribute.
You can use remove_avatar?.
#foo.remove_avatar? #=> true or false.
the avatar image will remove if true with save
the avatar image will not remove if false with save

Rails nested attributes by specific field name

I got my JSON posting to work with nested attributes. I can not post JSON records with the children models and it posts fine.
However, when I update a record I search for a field called "tracking_id" because the uploading program doesn't know the rails id for the model. Its a one way upload and remains agnostic to it. Using this field I can find the correct record and post an update. However the children to it get inserted instead of updated.
Is there a way i can tell it to find children based by a field I define , not just "id" since the client application will not know what that is?
Example:
#in Event Model
def self.batch_create(post_content, keymaster)
#keymaster is the user so we can add events to the right place
# begin exception handling
begin
# begin a transaction on the event model
Event.transaction do
# for each event record in the passed json
JSON.parse(post_content).each do |event_hash|
# create a new event
# if hash has tracking_id use that
if event_hash["tracking_id"].present?
event = keymaster.events.where(tracking_id: event_hash["tracking_id"]).first_or_initialize
else
event = keymaster.events.new
end
if event
# update the existing event record
event.update!(event_hash)
end # event.count
end # json.parse
end # transaction
rescue Exception => e
Rails.logger.info("Did not create batch events")
Rails.logger.info("caught exception #{e}! ohnoes!")
# Rails.logger.info(post_content)
end # exception handling
end # batch_create
What I'd like is the same function for the children of the Event record. so that if they are already on the event it will update them not just insert new rows.
One possibility is to loop through the nested params and inject the id field which Rails uses to check for an existing nested_attribute:
Not sure exactly how your params are constructed, but here's the pseudocode:
For each event hash in `events_attributes` nested attributes:
Find event by whatever attributes you've defined in your hash
Inject `id` of found event into the attributes hash
Now that you have an id field in your nested, Rails will update the existing record (i.e. upsert), rather than creating a new record.

Before create callback, how to validate if duplicate record exists and if so return?

Before a record gets created, I want to validate that a similiar record doesn't exist.
When I say similar, I want to compare 3-4 columns to see if they have the same value, if they do, then don't save and report back a validation message.
This only occurs on creation, there might be duplicates during updates etc. but during creation I don't want to allow duplicates.
How can I do this? i.e. which callback is best for this, and how to add to the validation errors collection?
I would do something like this:
validate :has_similar_record, :on => :create
...
private
def has_similar_record
if <Class>.find_by_column1_and_column2_and_column3(column1, column2, column3).present?
self.errors.add(:base, "record already exists with similar values")
end
end
You can use first_or_create to chain multiple where clauses and if it is not present create it.
For eg.
Post.where(author: 'tom').where(subject: 'Rails is awesome').first_or_create!
This will raise exception
More info here, (Assuming you are using Rails 3.2+)
http://guides.rubyonrails.org/v3.2.14/active_record_querying.html#first_or_create
Update
Sorry i misunderstood your question. You don't want to create the object if it already exists. So you can use exists? method which can be called on a model or relation.
For eg.
post_exists = Post.where(author: 'tom').where(subject: 'Rails is awesome').exists?
If it returns true, we can add error to self
self.errors.add(:post, "already exists") if post_exists == true
This can be wrapped in a custom validator method which will return false if post_exists is true
def validate_existence_of_post
post_exists = Post.where(author: 'tom').where(subject: 'Rails is awesome').exists?
if post_exists == true
self.errors.add(:post, "already exists")
return false
end
true
end
More info about exists here http://guides.rubyonrails.org/v3.2.14/active_record_querying.html#existence-of-objects

Rails :before_save, either create or update record and return false?

Have a form that has 1 field, an email address.
When submitted the model calls :before_save
Checks to see if the email address exists.
If it does, it creates a new record.
If it does not, it updates a record in another model AND NO record should be created.
Using return false to cancel the before_save for 2.2 but it rolls back the update and cancels the creation where I just want the record not to be created.
Am I on the right path? Is there a better way?
It is strange that you got user A's object, but update user B's row......
Maybe you could find the correct user object in the controller first:
#user = User.find_by_email(params[:email]) || User.new(params[:email])
User.find_by_xxx would return nil if it cannot find the corresponding object (or return the first object if there are two or more objects matched).
You could just make your own before_save method equivalent and call that instead of object.save
eg.
def custom_save
if email_address_exists? # this would be a method you create
self.save
else
# update record in other model
end
end
Then in your controller use this instead of save (ie. model.custom_save)

how does activerecord know to perform an insert or update?

Maybe some Ruby experts out there can shed some light on how activerecord know to do an insert or update when calling save(). What is the logic behind it? Does it check to see if the primary key is blank or something and if so does an insert, if not an update?
Whilst it's fine for some people to say "RTFM" I rather the more walk-through-but-still-entirely-useless-when-Rails-3-comes-out-and-changes-everything response:
How it works in Rails 2.3 (aka "today")
save calls create_or_update which looks like this:
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
result != false
end
You can ignore the first line of this method as it only raises an error if the record is readonly (it isn't usually, but in the case of joins it may be). What we are interested in here is the second and third lines inside the method.
The second line calls new_record? which is defined as this:
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
def new_record?
#new_record || false
end
And the variable #new_record is set when the initialize (new calls initialize, and gives us a new object, some background Ruby-fu here) method is called.
So if this #new_record is true it'll call create and if it's false it'll call update which brings us to what you're after, I think.
Furthermore, when you find a record it does not call initialize and therefore does not set #new_record. If you noticed, the code behind new_record? was #new_record || false, meaning it will return false if #new_record was not set.
Let's say for example you want to find the last Forum record, so you would do Forum.last.
This calls the last method on the Forum class, which inherits from ActiveRecord::Base
last calls the find class method.
find calls find_last
find_last calls find_initial
find_initial calls find_every
find_every calls find_by_sql
and find_by_sql calls instantiate
You'll see here that nowhere along this change is #new_record set and thus any record obtained by find will not be a new record.
Hope this helps you understand.
It principaly relies on the new_record? method.
This method returns true if it's a new record and false if it's not.
In fact it's not really hard.
When you get an existing record, it's not new. So new_record? can direcly return false.
When you create a new record (Model.new), new_record? will return true. It's a new record.
When you save that new record, it's not new anymore. The internal variable #new_record gets updated. new_record? won't return true anymore.
To see when it happens, go to ActiveRecord::Base, line 2911
self.id ||= new_id
#new_record = false
id
end
Check out activerecord's doc here and the source code there.

Resources