How to validate translated data? - ruby-on-rails

I am using Ruby on Rails (3.2.2), globalize3 (0.2.0) and batch_translations (0.1.2) ruby-gems. I would like to validate translated data handled by globalize3 as well as it should be. That is, for example, if...
... in my ROOT_RAILS/app/models/article.rb file I have:
class Article < ActiveRecord::Base
translates :title, :content
# This is needed to make the batch_translations to work.
attr_accessible :translations_attributes
accepts_nested_attributes_for :translations
validates :title,
:presence => true,
:length => ...
validates :content,
:presence => true,
:length => ...
...
end
... in my ROOT_RAILS/app/views/articles/_form.html.erb file I have:
<%= form_for(#article) do |f| %>
English translation:
<%= f.text_field :title %>
<%= f.text_field :content %>
Italiano translation:
<%= f.globalize_fields_for :it do |g| %>
<%= g.text_field :title %>
<%= f.text_field :content %>
<% end %>
<% end %>
I would like to validate data for title and content values when submitting the form and the current I18n.locale is not the I18n.default_locale. In other words, in order to store in the database correct information also for translations, I would like to validate title and content attributes when an user submit translation data for a locale language that is not the default English language.
Is it possible? If so, how?
Note: I took example from this question.

If I'm understanding you correctly, there's a few ways you could go about this.
1) A controller level test, where you test that a form submitted with the locale included persists to the correct localized content table. One way to do that would be something like this:
it "persists localized content for the Italian locale" do
Article.should_receive(:create).with(:locale => 'it', :title => 'title goes here', :content => 'this is content')
post :create, :locale => 'it', :title => 'title goes here', :content => 'this is content'
end
This basically says that the Article model should receive the create message with these arguments. I'm not entirely clear on how the batch_translations API works so you'll want to verify that this is how the parameters come in from that style form.
At that point, I think you'd be adequately testing that the data got persisted to the database correctly. Anything further would be testing that the Globalize gem works, which you want to avoid. Try and test the boundaries.

Related

Titlecase all entries into a form_for text field

Trying to title-case all the entries from a form_for field so they're consistent going into the database for searches.
Here is my search field (file created as a partial):
<%= form_for #airport do |f| %>
Input city
<%= f.text_field :city, :value => f.object.city.titlecase %>
Input country
<%= f.text_field :country, :value => f.object.country.titlecase %>
<%= f.submit %>
<% end %>
But when I run it I get a NoMethodError:
undefined method 'titlecase' for nil:NilClass
I took instruction on the .object.city.titlecase from this post.
Can anyone help?
You don't want to take care of normalizing your data in a view - what if the user changes the data that gets submitted? Instead you could take care of it in the model using the before_save (or the before_validation) callback. Here's an example of the relevant code for a model like yours:
class Place < ActiveRecord::Base
before_save do |place|
place.city = place.city.downcase.titleize
place.country = place.country.downcase.titleize
end
end
You can also check out the Ruby on Rails guide for more info.
To answer you question more directly, something like this would work:
<%= f.text_field :city, :value => (f.object.city ? f.object.city.titlecase : '') %>
This just means if f.object.city exists, display the titlecase version of it, and if it doesn't display a blank string.

Drop down list form validation alert in ruby on rails

I have drop down list and two radio button in a form.here is the code.i dont know how to set validations in controller.when not selecting value from drop down and one of the radio button,i have to show the warning messages 'please select value from dropdown' and check one radio button filed.how it is possible in ruby on rails while submitting a form.
<%= form_tag :action => 'show' do %>
<strong>Select device: </strong> <%= collection_select(:device, :id, #devices, :id, :name, options ={:prompt => "-Select a device"}) %>
<br></br>
<strong>Chose: </strong><%= radio_button_tag :name,:time, false, :onclick => "this.parentNode.submit();"%>Time
<%= radio_button_tag :name,:graph%>Graph
<% end %>
In device.rb i set the following but not showing any messages.
class Device < ActiveRecord::Base
attr_accessible :name
validates_presence_of :name
has_many :properties
def validate
if name == 'None'
errors.add_to_base("You must select a device name")
end
end
end
Why not use a validates_exclusion_of ? (or its equivalent in Rails 3)
http://apidock.com/rails/v2.3.8/ActiveModel/Validations/ClassMethods/validates_exclusion_of

Rails - Accepts_nested_attributes_for mass assignment error

I am currently trying to set up a form with nested fields on a belongs_to relationship, but I am running into a mass assignment error. My code so far is as follows (some html removed):
Sale model:
class Sale < ActiveRecord::Base
attr_accessible :customer_attributes
belongs_to :customer
accepts_nested_attributes_for :customer
end
new.html.erb:
<div class="container">
<%= form_for :sale, :url => sales_path do |sale| -%>
<%= sale.fields_for :customer do |customer_builder| %>
<%= render :partial => "customers/form", :locals => {:customer => customer_builder, :form_actions_visible => false} %>
<% end -%>
<% end -%>
customers/_form.html.erb
<fieldset>
<label class="control-label">Customer Type</label>
<%= collection_select(:customer, :customer_type_id, CustomerType.all, :id, :value, {}, {:class => "chzn-select"}) %>
</fieldset>
I believe this should allow me to create a Sale object, and a nested Customer object. The parameters being sent are (note some unrelated params are included):
{"utf8"=>"✓",
"authenticity_token"=>"qCjHoU9lO8VS060dXFHak+OMoE/GkTMZckO0c5SZLUU=",
"customer"=>{"customer_type_id"=>"1"},
"sale"=>{"customer"=>{"features_attributes"=>{"feature_type_id"=>"1",
"value"=>"jimmy"}}},
"vehicle"=>{"trim_id"=>"1",
"model_year_id"=>"1"}}
The error I am getting is:
Can't mass-assign protected attributes: customer
I can see why this might be the case, since :customer is not in the attr_accessible list for Sale - though shouldn't the form be sending customer_attributes instead of customer?
Any help / advice appreciated.
EDIT 1: As far as I can tell, attr_accessible in the Sale model should be covered with :customer_attributes - if anyone says different, please let me know.
EDIT 2: I have tried various permutations, but I can not seem to get the parameters to send customer_attributes instead of simply customer - perhaps I have missed a tag or used an incorrect tag somewhere in the forms above?
EDIT 3: I have found another question on SO that indicated a problem with the :url => part on the form_for tag - the question was referring to a formtastic setup, but I'm wondering if that could be what is causing the problem here?
This might be the problem... from the API docs:
Using with attr_accessible
The use of attr_accessible can interfere with nested attributes if
you’re not careful. For example, if the Member model above was using
attr_accessible like this:
attr_accessible :name
You would need to modify it to look like this:
attr_accessible :name, :posts_attributes
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Using+with+attr_accessible
I got to the answer here eventually. The key was this line:
<%= collection_select(:customer, :customer_type_id, CustomerType.all, :id, :value, {}, {:class => "chzn-select"}) %>
which needed to be changed to:
<%= customer.collection_select(:customer_type_id, CustomerType.all, :id, :value, {}, {:class => "chzn-select"}) %>
Once this was changed, everything fell into place.

rails simple_form fields not related to the model

I have an existing form which is tied to a model named 'Order', but i want to add new form fields that will capture Credit Card info such as name, cc number, etc to be processed on a 3rd party payment gateway.
But since i don't want to save CC info in our database, there are no corresponding columns of that in my order table. And this gives me an error when submitting the form that those Credit card input fields are not 'part' of the order model.
If I understand your answer correctly, what you want to do is explained in the official wiki page here: Create a fake input that does NOT read attributes. You can use a field not related to any real database column by Edward's suggestion, however you don't need to define an attribute in your model if the form field is nothing to do with the model.
In summary, the trick explained in the page is defining a custom input called 'FakeInput' and use it like this:
<%= simple_form_for #user do |f| %>
<%= f.input :agreement, as: :fake %>
....
Do not forget to restart your rails server after adding/modifying a custom input as Fitter Man commented.
UPDATE: Please note that the official wiki page has updated and the sample code on the wiki page is not working for those which use older versions of SimpleForm. Use code below instead if you encounter an error like undefined method merge_wrapper_options for.... I'm using 3.0.1 and this code works well.
class FakeInput < SimpleForm::Inputs::StringInput
# This method only create a basic input without reading any value from object
def input
template.text_field_tag(attribute_name, input_options.delete(:value), input_html_options)
end
end
You can use attr_accessor
class Order < ActiveRecord::Base
attr_accessor :card_number
end
Now you can do Order.first.card_number = '54421542122' or use it in your form or whatever else you need to do.
See here for ruby docs http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-attr_accessor
and here for a useful stackoverflow question What is attr_accessor in Ruby?
Don't get it mixed up with attr_accessible! Difference between attr_accessor and attr_accessible
The best way to handle this is to use simple_fields_for like so:
<%= simple_form_for #user do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= simple_fields_for :other do |o| %>
<%= o.input :change_password, as: :boolean, label: 'I want to change my password' %>
<% end %>
<% end %>
In this example, I have added a new field called change_password which is not part of the underlying user model.
The reason this is a good approach, is that it lets you use any of the simple form inputs / wrappers as fields. I don't care for the answer by #baxang, because it doesn't allow you to use different types of inputs. This seems more flexible.
Notice though for this to work, I had to pass :other to simple_fields_for. You can pass any string/symbol as long as there is not a model with that same name.
I.e. unfortunately I can't pass :user, as simple_form would try to instantiate a User model, and we'd get the same error message again...
Also if you're just trying to add something and get it into the params, but leaving it out of the model's hash, you could just do FormTagHelpers. http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html
Example:
<%= simple_form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %>
<%= devise_error_messages! %>
<% resource.class.invite_key_fields.each do |field| -%>
<%= f.input field %>
<%= hidden_field_tag :object_name, #object.class.name %>
<%= hidden_field_tag :object_id, #object.id %>
<% end -%>
I found a very simple (and somewhat strange) workaround.
Just add the input_html option with any value key inside. E.g:
= simple_form_for #user do |f|
= f.input :whatever, input_html: {value: ''}
Tested simple_from versions: 3.2.1, 3.5.1

How to edit a Rails serialized field in a form?

I have a data model in my Rails project that has a serialized field:
class Widget < ActiveRecord::Base
serialize :options
end
The options field can have variable data info. For example, here is the options field for one record from the fixtures file:
options:
query_id: 2
axis_y: 'percent'
axis_x: 'text'
units: '%'
css_class: 'occupancy'
dom_hook: '#average-occupancy-by-day'
table_scale: 1
My question is what is the proper way to let a user edit this info in a standard form view?
If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.
What is the best/proper way to edit a serialized hash field like this in Rails?
If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:
class Widget < ActiveRecord::Base
serialize :options
def self.serialized_attr_accessor(*args)
args.each do |method_name|
eval "
def #{method_name}
(self.options || {})[:#{method_name}]
end
def #{method_name}=(value)
self.options ||= {}
self.options[:#{method_name}] = value
end
attr_accessible :#{method_name}
"
end
end
serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end
The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:
#haml
- form_for #widget do |f|
= f.text_field :axis_y
= f.text_field :axis_x
= f.text_field :unit
Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.
However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).
You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.
Since I included " || {}", it will work with new/create forms, too.
= form_for #widget do |f|
= f.fields_for :options, OpenStruct.new(f.object.options || {}) do |f2|
= f2.text_field :axis_y
= f2.text_field :axis_x
= f2.text_field :unit
Have a great day
emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.
Here it is from top to bottom:
class Widget < ActiveRecord::Base
serialize :options, Hash
end
<%= form_for :widget, #widget, :url => {:action => "update"}, :html => {:method => :put} do |f| %>
<%= f.error_messages %>
<%= f.fields_for :options do |o| %>
<%= o.text_field :axis_x, :size => 10, :value => #widget.options["axis_x"] %>
<%= o.text_field :axis_y, :size => 10, :value => #widget.options["axis_y"] %>
<% end %>
<% end %>
Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.
I've been struggling with a very similar problem. The solutions I found here were very helpful to me. Thank you #austinfromboston, #Christian-Butske, #sbzoom, and everyone else. However, I think these answers might be slightly out-of-date. Here's what worked for me with Rails 5 and ruby 2.3:
In the form:
<%= f.label :options %>
<%= f.fields_for :options do |o| %>
<%= o.label :axis_y %>
<%= o.text_field :axis_y %>
<%= o.label :axis_x %>
<%= o.text_field :axis_x %>
...
<% end %>
and then in the controller I had to update the strong parameters like so:
def widget_params
params.require(:widget).permit(:any, :regular, :parameters, :options => [:axis_y, :axis_x, ...])
end
It seems to be important that the serialized hash parameter comes at the end of the list of parameters. Otherwise, Rails will expect the next parameter to also be a serialized hash.
In the view I used some simple if/then logic to only display the hash if it is not empty and then to only display key/value pairs where the value was not nil.
I was facing the same issue, after some research i found a solution using Rails' store_accessor to make keys of a serialized column accessible as attributes.
With this we can access "nested" attributes of a serialized column …
# post.rb
class Post < ApplicationRecord
serialize :options
store_accessor :options, :value1, :value2, :value3
end
# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"
# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)
# form.html.erb
<%= form_with model: #post, local: true do |f| %>
<%= f.label :value1 %>
<%= f.text_field :value1 %>
# …
<% end %>
No need setter/getters, I just defined in the model:
serialize :content_hash, Hash
Then in the view, I do (with simple_form, but similar with vanilla Rails):
= f.simple_fields_for :content_hash do |chf|
- #model_instance.content_hash.each_pair do |k,v|
=chf.input k.to_sym, :as => :string, :input_html => {:value => v}
My last issue is how to let the user add a new key/value pair.
I will suggest something simple, because all the time, when user will save form You will get string. So You can use for example before filter and parse those data like that:
before_save do
widget.options = YAML.parse(widget.options).to_ruby
end
of course You should add validation if this is correct YAML.
But it should works.
I'm trying to do something similar and I found this sort of works:
<%= form_for #search do |f| %>
<%= f.fields_for :params, #search.params do |p| %>
<%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect { |pt| [ pt.value, pt.id ] } %>
<%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
<%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
<% end %>
<% end %>
except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:
#search = Search.new(params[:search])
so its "half" working...

Resources