Update attributes for User only if attributes have changed - ruby-on-rails

The original, happily working version of my project looked like this:
1) User fills out form (new action) and hits submit (create action)
2) User is redirected to their edit page (edit action uses an edit_id created by model, not Rails auto-gen id), which shows the info user had already submitted
3) User can choose to change info (update action) and re-submit
In this version, even if the user changes nothing in the edit page and submits, the page will still flash a success alert.
From a database perspective, I don’t really care, because since the form is prefilled with the user’s info, the update_attributes method is just overriding old info with the same info.
From a user perspective though, it's annoying, so I want to ensure that the info is only updated and the success alert flashed only if the user actually changes something.
I thought this would be really easy, changing the old code from this:
def update
#request = Request.find_by_edit_id(params[:edit_id])
if #request.update_attributes(request_params)
flash[:success] = true
redirect_to edit_request_path(#request.edit_id)
else
render 'edit'
end
end
And adding one additional component to the "if" like this:
def update
#request = Request.find_by_edit_id(params[:edit_id])
if #request.update_attributes(request_params) && #request.changed?
flash[:success] = true
redirect_to edit_request_path(#request.edit_id)
else
render 'edit'
end
end
But this doesn’t work. Now what happens is that, on the edit page, if I don’t change any info and hit submit, nothing happens (which is great), but if I DO change info and hit submit, still nothing happens (which is bad). What am I doing wrong?
Note: I initially thought it was an order of operations error, so I made it a nested if, with first if #request.update_attributes, and second if #request.changed, but this didn't work either...

The update_attributes method includes the 'save' call as part of its method and is returning true if the save is successful. I think you're looking for something like this using assign_attributes:
def update
#request = Request.find_by_edit_id(params[:edit_id])
#request.assign_attributes(request_params)
if #request.changed?
if #request.save
flash[:success] = true
redirect_to edit_request_path(#request.edit_id)
else
render 'edit'
end
else
# Action if no change has been made
end
end

Related

unable to detect attribute changes

Accoding to this stackoverflow post, I can use the following to detect attribute changes:
self.changes
And it does work when I use it in the model, such in an after_update callback:
def check_activity
changes = self.changes
puts changes
end
Unfortunately, it is not working in my controller update action. I tried to use it before the task was saved and after the task was saved:
def update
changes = #task.changes
if #task.update_attributes(task_params)
changes2 = #task.changes
flash[:notice] = "Successfully updated task."
redirect_to polymorphic_path([#taskable, #task])
else
render :edit
end
end
Unfortunately in both cases, it is empty:
Empty ActiveSupport::HashWithIndifferentAccess
What might I be doing wrong?
There aren't any changes to show after you've made the change (IE update_attributes runs)
You could try using assign_attributes instead of update_attributes, checking that everything is valid (object.valid?) getting your changes and then saving
Just check out ActiveModel::Dirty
Changes method does not returns changes after save.you should use it in before save, if you want to check the value after save use these ones previous_changes etc. for detail checkout http://api.rubyonrails.org/classes/ActiveModel/Dirty.html.

Persisting ActiveRecord objects between requests

I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end

Manually create & sign in user while creating new record - using rails & devise

Basically, I'm working on a 'new listing' form, that needs to work whether or not the user is signed in - so if the user isn't signed in, it creates a user and signs them in, then creates the associated listing. I'm using rails 4 with devise, and really just need to know what the best practice is for this scenario, as it seems to be pretty common in web apps.
So, I've got the main user fields (email, pass, pass confirm) inside a fields_for :user tag at the bottom of the listing form, only shown if the user isn't logged in. Its inside the listing form, so the field names are in the form listing[user][email] etc. My create function inside the 'listings' controller currently looks like this:
def create
#if not logged in, create user & sign them in
if !user_signed_in?
#user = User.new(params[:listing].require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation))
if #user.save
sign_in #user
else
#listing.errors.add :base, "Invalid user data"
end
end
#listing = current_user.listings.build(listing_params)
if #listing.save
redirect_to listing_url(#listing), notice: 'Success! Now, add some images'
else
render "new"
end
end
This all works fine as long as there aren't any validation errors. I was thinking I could add render "new" after the #listing.errors.add line, but this doesn't appear to work (still gives an error with the "#listing = current_user.listings.build(listing_params)" line as the user isn't yet created/signed in. If I enter all the user fields, the user is successfully created & signed in, so it correctly shows the normal listing validation errors.
Anyway, I'm just getting a bit lost as to whether I'm going in the right direction with this - could someone shed some light on how to get this process working 'the rails way'? Thanks for any help!
You're almost there. In your question, you said that adding the line render "new" doesn't help. Well, that's because it's continuing to execute the rest of the function after that line. What you want to do is exit immediately, so you're not hitting the line #listing = current_user.listings.build(...).
You want render "new" and return after #listing.errors.add :base, ..., so that it leaves the action as soon as it sees that the user is not valid.

update_attributes isn't saving to the database

The correct custom params are being displayed in my debug function after the form is submitted but the default params are displayed when I enter console.
Controller
def update
current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
end
Model
attr_accessible :deficit_pct,
:target_bf_pct,
:activity_factor
Notes:
The closest question I could find to this on SO is a question that changes the attributes of an object that exists through an association.
I've tried using the Object.update method although I get an error that says:
private method `update' called for #
Any ideas?
Thanks!
Try the code :-
def update
if current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
else
p 111111111111
p current_user.errors.inspect
end
end
after check your log for any errors.exist for that active record
After playing around with in the console I've found out that even if I change the attributes manually the attributes don't 'stick' after I exit the console.
So I'll enter console, change the users attributes, test them, and they'll be changed. If I exist and re-enter, THEN test them, they'll have reverted back to their default values.
This leads me to believe that the 'after_initialize' method within the user model which sets its default values is running after each save. I though that it would only run after the object had been saved for the first time alone but now I know it run each time it is saved.
after_initialize :default_values
def default_values
self.goal = "Cut"
self.measurement = "US"
self.bmr_formula = "katch"
self.fat_factor = 0.655
self.protein_factor = 1.25
self.deficit_pct = 0.10
self.target_bf_pct = 0.10
self.activity_factor = 1.3
end
Once I remove all these values and the after_initialize method, it saves permanently.
You should make sure that you don't have any validation errors. You can check them using:
active_record_model.errors
In your case, it would be
current_user.errors
Also, you should print the return value from update_attributes to see if it's true or false. If you get false, the save was cancelled. This was most likely caused by validation errors or a callback returning false.
Something like:
def update
if current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
else
some_error_handling_code
end
end
Would not display success when the save fails. As a general rule, you should check whether a save, or any other back end operation, fails, before reporting success to the end user.

DoubleRenderError in restful_authentication with acts_as_state_machine when activating users

In a project which uses restful_authentication with acts_as_state_machine and email activation, I get a double render error whenever a user does the activation action from the email link.
I'm using the default
def activate
self.current_user = params[:activation_code].blank? ? false : User.find_by_activation_code(params[:activation_code])
if logged_in? && !current_user.active?
current_user.activate!
flash[:notice] = "Signup complete!"
end
redirect_back_or_default('/')
end
to activate, and the default
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
to redirect. The redirect method works in every other case it's called in the same way.
The double render error occurs at the render of the page main_page/home that is routed as "/".
What should I be looking for?
Acts As State Machine will sometimes have some odd behavior where the saved record written to the database will be out of sync with the object in memory. I bet you have a situation where the ruby object corresponding to the newly activated user is not being updated even though the field in the db is being set (of vice versa).
I'd need to see the controller action that actually runs to render the route you have setup to match "/", but I bet you've got subtly inconsistent cases in that action that are being tripped up by this inconsistency in AASM. Try reloading the user object at the start of that controller action to see if the problem goes away. If not begin debugging by making sure that your state changes are actually being saved out to the db.

Resources