I have a rails model that is filled by a very long form (split using the wicked-wizard gem).
There are some validations but I allow blank in most of the fields.
I need to take certain actions if the model is saved but some of the fields remain blank (for example remember the user to complete the form)and some other actions if the form 100% complete (for example sending the user an email to let him know the form is complete).
My idea is to trigger a virtual attribute such as :complete if there are not blank fields in my model, but I'm not sure how and where to do that.
Any hints?
=========================
EDIT
Thanks to #Kzu suggestion I've found this to work on my wizard controller (but could also work on the object controller itself)
def update
#customer = current_user.customer
params[:customer][:complete] = #customer.attributes.select{|key,value| value.nil? or !value.present? }.any? ? false : true
#customer.attributes = params[:customer]
render_wizard #customer
end
For example you can use an ActiveRecord callback and a complete boolean field for this form.
before_save :check_if_complete
def check_if_complete
# self.attributes returns a hash including the attribute name as key and its value as value
completion = self.attributes.select{|key,value| value.nil? or value.blank?} ? false : true
self.complete = completion
end
This solution could work but take care of the different attribute types you have in database.
Related
I am trying to send a notification email in my rails app only if the value of my column status was modified by the current update. I tried using Active Model Dirty as was suggested in some post and the status_changed? method. Unfortunately my email is never sent because #partnership.status_changed? constantly returns false even though the value of status was indeed changed during the last update. Here's my controller code :
def update
authorize #partnership
if #partnership.update(partnership_params)
send_notification_email
render json: {success: "partnership successfully updated"}, status: 200
else
render_error(nil, #partnership)
end
end
private
def send_notification_email
PartnershipMailer.partnership_status_change(#partnership).deliver_now if #partnership.status_changed?
end
I have also included Active Model Dirty in my model :
class Partnership < ActiveRecord::Base
include ActiveModel::Dirty
What am I doing wrong ?
.update also saves the model after updating it's data, therefore resetting the dirty-values. Try using .assign_attributes. It will just assign the attributes, then you can check for changes, and finally remember to save the model.
As #Thounder pointed out, the ActiveModel::Dirty method <attribute>_changed? is reset whenever you save a record. Thus, it only tracks changes between saves.
For your use case, what you want to use is the previous_changes method, which returns a hash with the key being the attribute changed and the value being an array of 2 values: old and new.
person = Person.new(name: "Bob")
person.name_changed? # => true
person.save
person.name_changed? # => false (reset when save called)
person.previous_changes # => { name: [nil, "Bob"] }
person.previous_changes[:name] # => returns a "truthy" statement if :name attribute changed
My pseudo-code may be wrong, but the principle works. I've been bitten by this "gotcha" before, and I wish the Rails core team would change it.
I understand their reasoning, but it makes more sense to me to track <attribute>_changed? after a save as well, because that seems the common use case to me.
You can try this method to check the changed attributes for the active record.
#partnership.changed.include?("status")
If it returns true then we have status attribute which was changed in this record.
Use #partnership.saved_change_to_status? or #partnership.saved_change_to_attribute(:status) as per docs.
Here is a one line method you can into the model which is the best for your case :
after_commit :send_notification_email, if: Proc.new { |model| model.previous_changes[:status]}
So i have my form and in my controller i have my update method as follows
def update
#student = Student.find(params[:id])
if #student.update_attributes!(student_params)
#student.read_notes = true
#here i check if the records changed or not?
ap #student.name_changed?
end
end
def student_params
params.require(:student).permit(:name, :email, :age, :class)
end
This fails as i always get the false response each time even though i have actually made changes to the name record.
How do i actually track my changes in my record if i am updating via this way?
When you save the record (which update_attributes!, update!, and update will all do), Rails' "dirty tracking" resets and you lose the ability to easily tell if anything changed. What you could do instead is use assign_attributes, like so:
def update
#student = Student.find(params[:id])
#student.assign_attributes(student_params)
if #student.name_changed?
# ...
end
#student.save!
end
There's also an ActiveRecord method called previous_changes, which stores changes made after a save. This article goes into detail on how to use that.
You could also simply track if the name parameter differs from the record's name, or store the value prior to the update and compare it afterward, depending on your needs.
Short question...
How do I edit an attribute passed to the model before validation or save?
Long question...
In my app I have a unique number generator so that entries can be indexed on an randomly generated unique number, so that users can not find that entry (In other words an alternative to using ID, which a user can randomly guess).
So in my model I have...
def self.generate_photo_number
record = Object.new
while record
random = rand(9223372036854775806)
record = Photo.first(:conditions => ["unique_photo_no = ?", random])
end
return random
end
And in my forms I have the input...
<%= hidden_field(:photo, :unique_photo_no, value: Photo.generate_photo_number) %>
i.e.
<input id="photo_unique_photo_no" name="photo[unique_photo_no]" type="hidden" value="233720368547758" />
Thing is, if the user submits the form, presses the back button, changes an option and submits again, the rails app will find an entry with that same unique_photo_no and cause a save error.
I could create a JavaScript function that generates a number on clicking submit (which I have done for pages outside the Rails app environment), but the beauty with the generate_photo_number function is that it checks to make sure it does not already exist - whereas the JavaScript does not.
How can I generate the number before validation and save it in the unique_photo_no attribute?
This is what I have put so far in my Photo model...
before_validation :generate_photo_number
validates_uniqueness_of :unique_photo_no,
:message => " - is already in use."
def self.generate_photo_number
record = Object.new
while record
random = rand(9223372036854775806)
record = Photo.first(:conditions => ["unique_photo_no = ?", random])
end
#photo.unique_photo_no = random
end
You will not be able to edit the attribute once it has passed to the Model.
Instead, edit the attribute before it is passed to the Model from the Controller.
For this question example, do the following...
Remove the form input field for unique_photo_no, as it is no longer needed
Add this to the 'create' action only of the Photos controller, beneath the #photo instance variable. Do not add it to the 'update' action, otherwise a new number will be generated each time it is updated.
def create
#photo = Photo.new(params[:photo])
#photo.unique_photo_no = Photo.generate_photo_number
.
.
.
end
Photo(being the Model).generate_photo_number(being the function called)
Rails callbacks NOT a CLASS method so you need to remove self in your model than i hope it will work.
if you want to validate the data while creating a new record mean you can use before_create callbacks
for your before validation callback should be in
before_validation :generate_photo_number
validates_uniqueness_of :unique_photo_no,
:message => " - is already in use."
def generate_photo_number
record = Object.new
while record
random = rand(9223372036854775806)
record = Photo.first(:conditions => ["unique_photo_no = ?", random])
end
self.unique_photo_no = random
end
it will automatically call when your hitting save or update.
I'm trying to make a pet rails app. My pet model includes two boolean values, hungry and feed_me. Right now, hungry and feed_me can both be set in the view, but I'm trying to set up the model so that if feed_me is true, hungry will automatically be changed to false. No matter what I do, however, feed_me never resets hungry. This is what I have in the model now:
attr_accessor :feed_me
before_save :feed
def feed
#feed_me = Creature.find(params[:feed_me])
#hungry=Creature.find(params[:hungry])
if #feed_me==true
#hungry=false
end
end
I'm new to Rails, but my understanding is that model should have access to the params hash, so I'm confused about why I can't use it to reset values.
You're on the right track using model callbacks, however models don't have access to the param hash - its available to controllers.
The model already knows the value of it's own attributes, so you don't need to get them from params. The controller I imagine is updating feed_me.
Also you shouldn't need to declare feed_me as an attr_accessor assuming it is backed by a database column.
You can change before_save to:
def feed
if self.feed_me
self.hungry = false
end
end
In your controller, I imagine you'd do something like:
def update
pet = Pet.find(params[:id])
pet.feed_me = params[:feed_me]
if pet.save
redirect_to pet_path(pet)
else
flash[:notice] = 'Error saving pet'
render :edit
end
end
I have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.