Rails only pass params that have changed on edit submit - ruby-on-rails

I have a edit form that prepopulates with the current values. Its a custom edit screen (not the default one that rails uses) and what Im using it for is for users to submit changes that will get voted on and might eventually get applied to the record. However, in the time it takes to be voted on something else might have changed and I dont want to overwrite the changes if they didnt submit a change.
EDIT: Changing to my more specific case so hopefully answers will work for it...
I have the following tables: Recipes, RecipeIngredients, RecipeSteps, RecipeChanges. On the show view of my recipes it displays all the ingredients/steps and there is a tab that then changes just the ingredients/steps to forms as to allow the user to submit changes. I dont want these changes applied though. Instead Im trying to create a voting system where people can vote on them. So what I have decided on is to convert the parameters from the form into a json string and save it in the RecipeChanges table under a single column (instead of using two table for ingredient changes and step changes). Heres the code for the form (html removed to make it easier to see the rails stuff):
<%= form_for #recipe, url: recipe_recipe_changes_path(#recipe), html: {method: "post"}, remote: true do |f| %>
<%= f.fields_for :recipe_ingredients, f.object.recipe_ingredients.order(:order) do |ff| %>
<%= ff.hidden_field :order, class: "position" %>
<%= ff.text_field :ingredient, placeholder: "Add Ingredient (e.g. 3 cups cooked rice)" %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= f.fields_for :recipe_steps do |ff| %>
<%= ff.hidden_field :order, class: "position"%>
<%= ff.text_area :step %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= submit_tag "Submit", class: "button" %>
<% end %>
So this sends a recipe object to my RecipeChange controller and there I handle the params to save them as the json string like so:
def create
#change = RecipeChange.new
#change.recipe_id = params[:recipe_id]
#change.new_recipe = recipe_change_params.to_json
#if #change.save
#add alert for successfully adding
#else
# add code for error handling
#end
end
This works like I want except for it saves all the ingredients/steps and I would like to only save what they have changed. I had two thoughts on how to do this but not sure how to accomplish it.
Check if the fields have changed when they click the submit button and only send the ones that have been edited (not sure if possible)
In the controller grab the original recipe (I have the id so that would be easy) and loop through the ingredients/steps and compare them and remove any that are identical....this is the method I think would be better but not sure how to loop through the hashes to accomplish this

Have a look at ActiveModel::Dirty. http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed
You can do something like:
changes = bag.changed_attributes and get a hash of that attributes that changed, and then save those with bag.update_attributes(changes), for example.

This is a bit old now but I've come across the same or similar scenario and wanted to share for others.
In my case I populate some nested form fields based on an existing object in my #new action. However, in my #create action I did not want to save these nested form params unless they were actually modified compared to the original existing object.
In this case, ActiveModel::Dirty would always be true as it would compare [nil, "value"].
I first tried to modify the params in my #create action and compare them to the original existing object similar to this discussion but this got messy and felt wrong.
I ended up saving all records then doing a cleanup with an instance method in my model that I call after save in my controller's #create action. Still feels a bit dirty but it's working.
Example:
# controllers/changes_controller.rb
# ChangeController#create
def create
# ... shortened for example ...
if #instance.save
#instance.remove_clean_changes
format.html
end
end
# models/change.rb
# Change#remove_clean_changes
# Loop over all original objects and compare the necessary attributes
# to the changes. If they match, they are clean and should be deleted.
def remove_clean_changes
original_objects = self.original_objects
changes = self.changes
original_objects.each do |original_object|
changes.each do |change|
change.destroy if (change.attribute_one == original_object.attribute_one &&
change.original_object_id == original_object.id)
end
end
end

Related

Edit/create nested resources in Formtastic (Rails)

This question is regarding Rails 4/postgresql and the app is hosted on Heroku.
I am making a Quiz-functionality on a website and I am wondering on how to implement the forms (using Formtastic) best to make this is easy as possible. I have three models:
Quiz (has_many :quiz_questions), e.g. "Test to see how awesome you are"
QuizQuestion(belongs_to :quiz, has_many :quiz_options). e.g. "1. Which is your favorite color")
QuizOption (belongs_to :quiz_question). e.g. "Blue"
I have set up the forms like this:
<%= semantic_form_for([:admin, #quiz], :url => admin_quiz_path(#quiz.id)) do |f| %>
<%= render 'form' , :f => f %>
<% end %>
where the form looks like this:
<%= f.inputs %>
<h3>Quiz questions</h3>
<%= f.semantic_fields_for :quiz_questions do |qq_f| %>
<%= qq_f.inputs %>
<h4>Quiz options</h4>
<%= qq_f.semantic_fields_for :quiz_options do |qqo_f| %>
<%= qqo_f.inputs %>
<% end %>
<% end %>
<%= f.actions do %>
<%= f.action :submit %>
or go <%= link_to 'back', admin_quizzes_path %>
<% end %>
It seems, however, not to be working the way I want. I expect to be able to see the fields of QuizQuestion and QuizOptions in this form (there are objects for those) but I don't.
More importantly is that I would like to be able to create a New QuizQuestion and subsequently QuizOption in this form. It doesn't necessarily have to be jQuery/ajax or anything but I would like to do it all from this form.
Basically, I would like my workflow to be like:
Create a Quiz and add values to it. Click Create.
Add QuizQuestion number one and add the values to it (like "name label"). Click Create.
Add QuizOption related to QuizQuestion number one, and its "name label". Click create.
Repeat for QuizQuestion/QuizOption until the Quiz is done.
How can I do this?
For your workflow you might have to add accept_nested_attributes_for for the nested resources, this way when creating an object object you can actually create nested children (as long as they fulfill all the validations). This way:
# A quiz :has_many :quiz_questions
#quiz = Quiz.create(...)
with a declaration like:
has_many :quiz_questions
accepts_nested_attributes_for :quiz_questions
in your Quiz model you'll actually be able to create QuizQuestion from the quiz model like:
# using the previously quiz model
quiz.quiz_questions.create(...)
Doing the same for the deeply nested associations will do have the same effect.
Perhaps the reason why you don't see any field on the form is because there is not nested object created. Let me explain. When you create a new Quiz object, in your quizs_controller (or whatever the inflection for quiz is...) you need a:
def new
quiz = Quiz.new()
end
and
def create
Quiz.new(quiz_params)
end
private
def quiz_params
# whitelisted parameters sent along with the form
params.require(:quiz).permit(...)
end
if you actually want to be able to see the fields in the form you'll have to use the build method and actually populate that new object with respective the nested resources.
Note that for this to work with the form you will have to whitelist in the quizzes_controller the right attributes. You can debug the params you receive once you send the new quiz formulary and check that everything is right.
TIP! if you don't want to worry about the JS when adding nested resources dynamically, I recommend you using the cocoon gem

How to create new text field

I know this is probably a very basic question but I am brand new to Ruby and kinda in a dead end. I have made a simple little site with profiles and profile pages. But on the profile pages I would like to add a new text field like "Bio" for instance where the user types in a bio about himself and it shows. Im just at a blank on how to create a new text field where people can input this stuff. I know this is basic stuff just stuck and looking for some help or guidance to a tutorial or something. Thank you in advance
Here's an example copied from another answer:
<%= form_for(:ad, :url => {:action => 'create'}) do |f| %>
<%= f.text_field(:name) %>
<%= f.text_area(:text, "", :size => "50x10") %>
<%= submit_tag("Submit") %>
<% end %>
This is kind of a complicated question, once you think about it, because there are so many parts.
Ruby on Rails is built on a architecture, called Model View Controller or MVC. The three parts together make the user interface that is presented to the user.
Models are the actual data, like the User objects, in this case. To create the model, type in this command to the console:
rails g model User bio:text name:string
This will make a basic user model, which only contains two columns, a column for the bio, and a column for their name. Note that this is very uncomplicated, and this can be expanded on a lot, but for now it will do.
Or, if you already have a user model, type in this command to the console:
rails g migration add_bio_to_users bio:text
Next are the controllers, controllers are, in a way, what connect the models and the views, so they manage all of the logic in the back end, like creating new users, or adding bios to their profiles.
You can create the user controller like this (if you do not already have one):
rails g controller Users new
And then, you can add this code to the new file generated, to add the functionality of adding bios (and showing them, too) (and updating other columns as well):
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
render #user
else
render #user # Handle error accordingly
end
end
def show
#user = User.find(params[:user])
end
private
def user_params
params.require(:user).permit(:name, :bio)
end
Now, to the final part, which is the view, which is the actual thing that is presented to the user:
<%= form_for(:user, :url => {:action => 'update'}) do |f| %>
<%= f.text_field(:name) %>
<%= f.text_area(:bio, "", :size => "50x50") %>
<%= f.submit yield(:button_text) %>
<% end %>
Note that this is just a simple view that assumes that you also have a column name in your User model, you can change this accordingly.
And, finally, to show the user, add this to the show view:
<%= #user.bio %>
to show the bio in the show view.
Good luck!

Ruby on Rails: Use form_for on a model, and flip the form value

I've got a form_for form, and within it, I'm using a form helper check box. The model has a field that's a boolean value, but I'd like to be able to display it on my form in terms of its converse since I think that's easier for users to understand.
Is there any way for me to have a Rails form helper act as if the model's boolean field is flipped?
Example:
<% form_for :user do |form| %>
<%= form.check_box :privacy_disabled %>
<% end %>
In this example, the User model has a boolean field privacy_disabled. I would like to display this in the form as check here to enable privacy.
The checkbox helper function has the ability to set its checked and unchecked values, but inverting these does not seem to properly populate the checkbox with the pre-saved value.
This is how I ended up solving this issue, I was actually close with my first attempt:
<%= form.check_box :privacy_disabled,
{:checked => !#user.privacy_disabled},
0,
1 %>
So the values returned by the checkbox are flipped, and whether it starts out checked is manually flipped as well.
Not the most elegant solution, but you could do something like the following:
Add these methods to your user model:
def privacy_enabled=(val)
self.privacy_disabled = val == "0"
end
def privacy_enabled
!privacy_disabled
end
Change the form to:
<%= form.check_box :privacy_enabled %>
Again, not elegant, but it works.

Rails: textfield list to array of strings

I want to take input from a textfield and turn it into an array of strings. After having submitted the post request, I want to display again the textfield, but including the values of the textfield in an array.
I have a view that would look like:
<% form_tag "/list2array" do -%>
<%= text_area_tag "mylist" %>
<div><%= submit_tag 'save' %></div>
<% end -%>
<% #myArray.each do |item| %>
<%= item %>
<% end %>
And as a start for the controller:
class List2ArrayController < ApplicationController
def index
end
def save
#myArray = params[:mylist].split("\r\n")
end
end
However, after the post, I only get an empty textfield without values in the array from the previous POST.
Do I need to use the model layer for my experiment? How? Or do I need to modify my controller?
Sort answer: Yes. You need to use some form of data store, either models or you can store it in the session. The are no continuations of state.
If you have a model you can add an attribute called mylist and mylist_array (you can use serialize for the array). Then either with a setter, or a before_validations callback set the value of mylist_array as you do in your example.
On a slightly contradictory note: adding the following to the end of your save method, will make your experiment sort of work, but you will need to fix your form post url first or add in a route for it manually.
render :index

ruby on rails form_for

I have a calendar_date_select in a view that shows a table listing all the information on a certain phone. I want to add a To: and From: date range that a user can select and update the table in the view. The structure is like this:
Usage Controller
Detail action in the usage controller that shows the call history from a certain phone.
Inside detail I want the To and from fields with a refresh button.
What is exactly happening in this code:
<% form_for :date_range do |f| %>
<%= f.calendar_date_select :start, :time => true %>
<%= f.calendar_date_select :end, :time => true %>
<%= f.submit "Submit" %>
<% end %>
Does this pass a hash to the usage controller and look for a date_range method? My current route looks like this
usage/detail/id-of-phone
I would like it to look something like this:
usage/detail/id-of-phone#start-end
So I could then (I think) extract the start and end dates from the params with just params[:start] and params[:end]. Am I doing this right, or is there a better way to get the desired result that I want.
I haven't used the calendar_date_select plugin, but you should be getting the parameters back already.
params[:date_range][:start]
params[:date_range][:end]
What you want is the url or the smart solution to get the params?
Please set the routes.rb for the url. Or you can make many method in the 'DataRange' model.
As many programmers using, save many dates in the model. But making us terrible is using the params smartly.
Such as
class Model
def start
......
end
def end
......
end
end
You can't get the params by params[:start] if you pass the params by the form. You can see the form's html for detail.
Please use the
params[:...][:start]

Resources