Keeping form data in Rails - html

I have an HTML form in Rails. Sometimes when the user submit data, my server will reject it because the data is not appropriate (say, the user types a non-number in a field where there's supposed to be only numbers.)
<%= form_tag "myproject/win", :id => "win_form" do -%>
...
<label>
Year
<%= text_field_tag "year", '', :size => 20 %>
</label>
...
<%= submit_tag "Go!" %>
<% end -%>
In that case, my controller returns an error message
#notice = "Year must be a number"
return render action: "new"
But all the data that the user typed in disappears, and the user has to restart with a blank form. Is there a way to keep that data for the user?

That's fine if you don't have a data store behind this, but you can still leverage ActiveModel to create an object that you can use to help build forms (or use the reform gem). If you really don't want to do that, despite the additional capabilities it affords you, you can just use what you have now, but pay attention to the arguments:
From http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-text_field_tag:
text_field_tag(name, value = nil, options = {})
Creates a standard text field; use these text fields to input smaller chunks of text like a username or a search query.
Your code:
text_field_tag "year", '', :size => 20
So you're passing in '' as the value, and asking us why the value is ''. Perhaps what you really want is this:
text_field_tag "year", params[:year], :size => 20

Related

Rails - simple form - adding html options to collection select

I am trying to make sense out of the instructions in this sheet:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
My objective is to make the select menu wider than the default width.
I have this form input:
<%= f.input :trl, label: false do %>
<%= f.select :trl_id, Trl.all.map { |t| [t.try(:title), t.try(:id)] },
include_blank: false,
prompt: 'Select one',
input_html: { "width: 200px" }
%>
<% end %>
At the moment, the default width html for the select drop down is super tiny. I'm trying to get the select to accept a html attribute for width.
I have tried about 50 different permutations of the above. I can't find a way that works.
From the instructions in simple form, I'm directed to the instructions in the above link. Those tell me that the structure of the input form field should follow this format:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
From what I can decipher, I think I need as many comma separated fields as are in this example. So that's 7 in total. That's an assumption because I can't tell if you just skip the ones that I don't want to use or do something else to indicate that there is no entry for that particular field. Following that assumption, I have:
<%= f.input :trl, label: false do %>
<%= f.collection_select(:trl_id, Trl.all.map { |t| [t.try(:title), t.try(:id)] },
include_blank: false,
prompt: 'Select one',
html_options = { width: 200px })
%>
<% end %>
This gives me an error that says:
syntax error, unexpected tIDENTIFIER, expecting '}'
html_options: { width: 200px })
^
I can't find an english language translation of what tIDENTIFIER is or means. Most stack overflow questions referencing this term generally indicate that something is wrong with the syntax.
I'm struggling because I can't understand the apidock instructions in the first place. Do I need to add some more blank fields along the way? If I do, do I just write two commas in a row to indicate a blank field?
There are 7 fields in the api dock example. I think my attempts are missing content for value method and text method. I don't know how to indicate to rails that I don't have any content for them. None of the examples in the API dock indicate blank fields, so I am thinking that less than 7 fields should be acceptable.
I'm also not sure where else to search for the definition of tIDENTIFIER. There may be a clue in that term that I can't access because I can't find the meaning of the term.
Can anyone help?
Late response, but see:
https://api.rubyonrails.org/v7.0.2.3/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
and
How do I set the HTML options for collection_select in Rails?
Essentially, the collection select definition looks like
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
as you rightly mentioned in your question, therefore try to apply your pre-defined css classes within the html_options curly braces. If you have nothing to declare for the options {} that come before the html_options (e.g disabled options), leave it empty, e.g.
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {class: "driver_name_css"} %>
I believe your code would look like:
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {style: "width: 200px"} %>
or
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {class: "pre-defined-css-class-with-desired-width"} %>

Submitting an empty text field as an empty string, not null, in a multipart form

This is happening in my Rails app, but I'm not sure whether this is an issue with Rails or if I'm misunderstanding how multipart forms are supposed to work.
Here's (something like) my form:
<%= form_for #user do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
User#name has a presence validation. If I visit users/1/edit, empty out the value of the 'name' text field, and click submit, nothing gets updated because the presence validation fails. So far, so good.
But now I've added an avatar attribute to my User (using the Paperclip gem for file storage), so I update the form accordingly:
<%= form_for #user do |f| %>
<%= f.text_field :name %>
<%= f.file_field :avatar %>
<%= f.submit %>
<% end %>
Now, if I go to edit the user and submit a blank value for name, the validation doesn't fail. User#name keeps it previous value, but the update still appears to be successful (i.e. I don't get an error message about failed validations and the updated_at timestamp in the database gets updated.)
On closer inspection, it seems that when I include a file_field in the form, it changes the form's behaviour when submitting blank text fields (probably due to the fact that form_for now outputs a form with enctype=-"multipart/form-data").
When the file_field isn't present, submitting a blank name sends these params to the server:
{ "id" => 1, "user" => { "name: "" }
Which results in something like User.find(1).update_attributes(name: "") in the controller, which of course fails to update because Rails sees that we're trying to update 'name' to a blank string and the validation fails.
When it is present, this gets submitted:
{ "id" => 1, "user" => { } (plus extra info about the avatar file)
The "name" key isn't present at all, so the controller runs User.find(1).update_attributes() which of course passes as there's nothing being updated that might fail a validation.
Is this a bug, or a feature? Why would changing the enctype to multipart (assuming that's the source of the problem) change the way blank text fields behave? (I've tested this in both Chrome and FF, fwiw.) If this is really the intended behaviour, how can I ensure that blank text fields get submitted properly without having to add a bunch of tedious boilerplate around every text field?
(If it matters, I'm using: Ruby 2.3.0, Rails 5.0.0.beta3, Paperclip 5.0.0.beta1, and I've tested in Chrome 49 and Firefox 45)
I had the same issue, updating Rails to the latest version fixed it.
I think you can do something like that
updated_user = User.new(your_params) # The new attributes is set to a new object record
target_user = User.find(params[:id]) # the user you want to update
target_user = updated_user
if target_user.save?
..
else
..
by this all the validations will be triggered and the active record will have all the new attributes, if the name is blank it'll catch it
The following strong param method won't bring back the dropped :name parameter, but it should prevent the form from imitating a successful submit:
private
def user_params
params.require(:user).permit(:name, :avatar)
end
This can be inserted as a private method under your Users Controller. Then simply call user_params in your Update method.

Rails from a high-level view: performing calculations on a model value between view and controller

This must be a common need but I can't seem to find a definitive answer on the most rubyesque way. I need to create a fairly complex algorithm to dynamically calculate course grades in a rails 4.1 app.
Specifically, I have a model, "course", and whenever an instance of it is displayed in the view, I want to dynamically calculate the current grade (a decimal value, calculated from many course.field values) and display it as a letter value using a switch/case. My assumption was that I could do this in the controller (but it almost seems like it's complex enough to warrant it's own -- module? In C++ I would create a class). At the same time, since it is created dynamically, it seemed like bad form to create a current_grade field for it in the model, so it's not one I can pass back and forth as one of the allowable params (that I know of-- can one pass a variable in the params that is not represented in the db?).
In my initial research I see suggestions of hidden_field_tags and helper_methods and all_helpers and modules and global modules and more. Under time pressure, I dread beginning down the wrong path. Which is the better approach? Or a good high level doc for reference?
As an example, here is one view in which I would like to calculate current grade, compare it to desired grade, and display accordingly.
# index.html.erb
<% #courses.each do |course| %>
<li>
<%= my_algorithm_to_calculate_curr_grade(many course.fields used to caluculate)
<= course.desired_grade ? "set li to <Color: red>" : "set li to <Color: green>" %>
<%= course.course_name %>
Current Calculation: <%= display_results_of_previous_calculation %>
(Goal: <%= course.desired_grade %>)
<%= link_to 'Show', course %>
<%= link_to 'Edit', edit_course_path(course) %>
<%= link_to 'Drop Course Without Penalty', course, method: :delete, data: { confirm: 'Are you sure?' } %>
</li>
<% end %>
It's hard to tell from your question if course.fields are attributes of Course or different model(s). If all the fields are Course attributes, I would put it as an instance method on Course.
class Course < ActiveRecord::Base
def calculated_grade
# fun algorithm
end
end
If course.fields need to be loaded from the database, I'd probably go with a Plain Old Ruby Object (PORO), maybe call it CourseGradeCalculator (put it in app/models, why not? It's business logic)
class CourseGradeCalculator
attr_reader :course, :fields, :grade
def initialize(course, fields)
#course = course
#fields = fields
#grade = calculate_grade
end
private
def calculate_grade
# fun algorithm
end
end
# controller
#course = Course.preload(:fields).find(params[:id]
# view
CourseGradeCalculator.new(#course, #course.fields)

How to use Ruby Formhelper with a SELECT element

Using Ruby formhelper,
I want the user to see a popup like "fill this out" when they try to submit the form with any empty fields or unselected selects.
To require a text field, this works:
<%= f.text_field :name, :required => "required" %>
To require a select, I'm trying this but it doesn't work:
<%= f.collection_select :metric, Metric.all, :id, :name, :prompt => true, :required => "required" %>
The select is there and its options are populated correctly in the dropdown. But the user should see a popup if they try to submit without selecting another option besides the default "please select" - it never appears.
You need to use Ruby's select tag combined with Ruby's options_from_collection_for_select. You can set your prompt to true or add a custom prompt and required must be in curly braces and its value set to true. For example:
<%= select('trophy','metric', options_from_collection_for_select(Metric.find(:all), :id, :name),{:prompt => 'Select Metric'},{:required => true})%>
Where:
-trophy is the name of an instance variable or a model object
-metric is is the attribute of that instance variable. This is typically a field/column of the table whose data you're displaying.
-:id is the key
-:name is the result

add comparison feature in rails

i'm having a bit of trouble with adding a certain feature. i'm working on a buy/sell site and i want to be able to compare posts. here's what i have so far:
in the posts view:
<%= button_to "Add to Compare", :action => "addCompare" %>
in the corresponding controller:
##a = Array.new()
def addCompare
##a << Post.id
end
so, all i want to do is add the post's id to the array ##a. when i test this, i click on the "Add to Compare" button and I'm welcomed with this:
Template is missing
Missing template posts/addCompare with {:locale=>[:en, :en], :formats=>[:html], :handlers=>[:rxml, :rjs, :builder, :rhtml, :erb]} in view paths "/home/mja32/470repo/traders/app/views", "/var/lib/gems/1.8/gems/devise-1.4.2/app/views"
So I guess it's trying to redirect to a view. How do I prevent it from doing this? All I want this button to do is to add the post's id to the array and nothing more.
Thanks in advance,
Matt
First of all, storing persistent data in a controller's class variable isn't going to work the way you want it to. There's no guarantee that ##a will be the same array on your next addCompare call; for example, your next addCompare call could be handled by a different process. Also, what happens if two different clients call addCompare? Do you really want to mix their data together in one pile? Probably not. Your first task is to replace ##a with a real per-user persistent store.
If you want to return nothing at all from your controller, just do this at the end of your controller method:
render :nothing => true, :status => :ok
That will tell Rails that something has already been rendered so it doesn't need to try the default rendering action (which is to render the posts/addCompare view) and returns nothing more than a 200 status code to the client.
Once that's in place, you'll probably want to AJAXify your button with :remote => true:
:remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the submit behaviour. By default this behaviour is an ajax submit.
So this:
<%= button_to "Add to Compare", { :action => "addCompare" }, { :remote => true } %>
Note that button_to looks like this:
button_to(name, options = {}, html_options = {})
and that :action is for options but :remote is for html_options so you have to explicitly set up the hashes with {}; you could just wrap the options in braces:
<%= button_to "Add to Compare", { :action => "addCompare" }, :remote => true %>
but I prefer the consistency of wrapping them both by hand.