How to access nested child element in fields_for - ruby-on-rails

I'm trying to access a Hash type of mongoid in fieds_for and I already have a relationship with a model and want to access a hash of that model. Something like:
class Leave
field :leaves_types, :type => Hash
end
class User
has_many :leaves
end
Want to do something like:
form_for #user do |f|
f.fields_for :leaves.leave_types...
How I can achieve this? Thanks in advance.

You should give a block to fields_for. For more information on that method see docs. In your case, first, add this line to your User model:
class User
has_many :leaves
accepts_nested_attributes_for :leaves
end
This is required so that when your form is posted, the attributes coming from the form fields for leaves via params were handled correctly.
Now your template should look like this (for simplicity by now I assume that your Leave also has a simple text field named foo):
<%= form_for #user do |f| %>
...
<%= f.fields_for :leaves do |leave_fields| %>
# Fields for a leave here ----v
Foo: <%= leaves_fields.text_field :foo %>
<% end %>
<% end %>
Or, if you #user.leaves already initialized and you want form builder to put its values to form fields, you have to iterate over #user.leaves, passing each of them to fields_for as second argument:
<%= form_for #user do |f| %>
...
<% #user.leaves.each do |leave| %>
<%= f.fields_for :leaves, leave do |leave_fields| %>
# Note the addition here --^
Foo: <%= leave_fields.text_field :foo %>
<% end %>
<% end %>
<% end %>
But your question has another one inside: you have not a text field, but a hash, and there is no default form input for it (i.e. there is no f.hash_field :leaves_types). So you may want to make it by yourself like suggested in these questions: [1], [2] and [3].
Anyway, having a Hash field seems rather uncommon to me, so maybe Hash can be somehow replaced, say, with another has_many association (not sure), and in this case you will only need another nested fields_for.

Related

How to deal with serialized field in rails form

I have a serialized field in my model
class Screen < ActiveRecord::Base
serialize :options
end
The user should be able to add / edit n number of options for each record. I saw this SO question and tried
<%= 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 %>
but my problem is I don't know what are the fields user want to add, and user can specify variable number of attributes foroptions. What is the best/proper way to to do this ? Any help much appreciated. Thanks
I've never seen serialize before, so I looked it up. Here's a tutorial; apparently you need to specify the type of the serialized object as well:
serialize :options, Hash
To whitelist the hash attrributes, you have a couple options.
You could create a custom validator (see here for instructions)
You can also overwrite the options= method:
def options=(val)
whitelisted_keys = ["some", "keys"]
if val.is_a?(Hash) && val.keys.all? { |key| whitelisted_keys.include? key }
super
else
errors.add(:options, "is invalid")
self
end
end
You also might need to configure your screen_params method, so if things aren't working show that code in your question.

In ruby on rails 4, how do I set an attribute in the forms_for helper that is not associated with a model?

I want to be able to access :to_whom text value via params[:to_whom] in the controller. To_whom does not exist in a model.
I get the sensible error:
'undefined method `to_whom' for Conversation'
How can I add an arbitrary attribute to pass back to the controller in rails?
Also, in my view I did Message.new and Conversation.new which is incredibly ugly. I initially set #conversation = Conversation.new in the controller, however I found I had to recreate those variables in the second controller method anyways, which makes sense (after I hit the submit button). Thus instead of setting #message, #conversation in the new method, I removed all the lines from new and did the .new syntax in the view. Is there a more elegant way of writing this code so it isn't so hacky feeling?
CONTROLLER:
class ConversationsController < ApplicationController
attr_accessor :conversation, :user, :to_whom
# this is the method that generates the below view
def new
end
def create
...
end
end
VIEW:
<%= form_for([current_user, Conversation.new]) do |c| %>
<%= c.label :to_whom %>
<%= c.text_field :to_whom %>
<%= c.label :subject %>
<%= c.text_field :subject %>
<%= form_for(Message.new) do |m| %>
<%= m.label :message %>
<%= m.text_field :text %>
<div class="actions">
<%= submit_tag "send" %>
</div>
<% end %>
<% end %>
Virtual Attributes
Your attr_accessor belongs in your model (not your controller). Currently, you have it stored in your controller, which will do nothing at the model-level:
#app/models/conversation.rb
Class Conversation < ActiveRecord::Base
attr_accessor :conversation, :user, :to_whom
end
#app/controllers/conversations_controller.rb
# remove attr_accessor
You have to remember that since Ruby is object-orientated, all the data objects you get are from the model. This means if you call #conversation = Conversation.new, the attributes of that model are actually created in the conversation model
Normally, the model will set the attributes in accordance with your database columns. If you don't have a database column present, you need to create the relevant getter / setter methods using the attr_accessor module
m.text_field :to_whom is just a helper to build an html input tag. You could write your own in raw html, filling in the blanks with erb tags, or you could use other helpers, such as text_field_tag:
text_field_tag "to_whom", params[:to_whom]
I think you need to use fields_for helper, It should be something like below,
= c.fields_for :message do

Form has_many in text_area

I have the following situation:
A Order has many Pages. I want to let the User to paste a bunch (20+) URLs (it's a Page attribute) that they might have in a doc file into a text area.
Right now I am not using a Form associated with an Order object, because I fail to see how I can do a nested form of the URLs if those are inside a text area.
I have seen a similar question has been asked before here: Rails: Using a Textarea for :has_many relationship , but I fail to see how would I code the view and model in order to do so.
So, if I have this:
Order has_many Pages
And a form like this:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls?? %> # This would let the user paste X URLs, which would be
# used to create X Pages associated with the Order.
<% end %>
You could retain the view code that you have:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls %>
#other field and submit button
<% end %>
In your model, you'll need to do the following:
attr_accessor :page_urls
after_validation do
if page_urls
parse_page_urls.each do |url|
pages.create(url: url)
end
end
end
def parse_page_urls
#use regexp to extract urls from page_urls string and return an array of url strings
end
The accessor is defined so that you can use :page_urls in your form_builder. You could set easily validations in your model for :page_urls that way too.
Once order has been validated, it will create page objects according to the number of urls extracted from the page_urls attribute.
You could refer to this for some help with using regexp to extract the urls from the string.
Hope that helps!
This is a job best handled with nested form. It will let you submit attributes of a has_many relationship model from the parent model, like you wish to do. For example, from its docs:
Imagine you have a Project model that has_many :tasks. To be able to use this gem, you'll need to add accepts_nested_attributes_for :tasks to your Project model. If you wish to allow the nested objects to be destroyed, then add the :allow_destroy => true option to that declaration. See the accepts_nested_attributes_for documentation for details on all available options.
This will create a tasks_attributes= method, so you may need to add it to the attr_accessible array (attr_accessible :tasks_attributes).
Then use the nested_form_for helper method to enable the nesting.
<%= nested_form_for #project do |f| %>
You will then be able to use link_to_add and link_to_remove helper methods on the form builder in combination with fields_for to dynamically add/remove nested records.
<%= f.fields_for :tasks do |task_form| %>
<%= task_form.text_field :name %>
<%= task_form.link_to_remove "Remove this task" %>
<% end %>
<%= f.link_to_add "Add a task", :tasks %>
In response to your comment:
In order to do something like that, you would need to do processing in the controller to separate the URL's, then make a new Page object associated with #order object. Unfortunately, there isn't a way to do this without post-processing, unless you do it with JS on the client side with hidden inputs.

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

Nested forms in rails - accessing attribute in has_many relation

I have a user and a profile model. One user can have many profiles. I need to access only one information from the profiles section (viz the phone number) in my user model during the user creation process. Hence I'm trying to get it done through attr_accessible. My user.rb looks like this.
has_many :profiles
attr_accessible :handle, :email, :password, :profile_mobile_number
attr_accessor : :profile_mobile_number
The problem that I'm facing is that when I try to call the getter method profile_mobile_number in a method in user.rb (the method is private, though I think it doesn't matter), I'm getting a null value. I use the following in my users/new.html.erb form
My question is what is the right way to do this? Should I use <% f.fields_for :profile do |ff| -%> or <% f.fields_for :profiles do |ff| -%> (notice that the second one is plural). When I use the plural :profiles, I don't even see the fields on the form. What am I missing here? And what is the tense that needs to be used in model user.rb? :profile_phone_number or :profiles_phone_number? Thanks.
You could do something like the following:
<% form_for #user, :url => { :action => "update" } do |user_form| %>
...
<% user_form.fields_for :profiles do |profiles_fields| %>
Phone Number: <%= profiles_fields.text_field :profile_mobile_number %>
<% end %>
<% end %>
But since you already have an association, then might as well use 'accepts_nested_attributes_for'
You should watch RailsCasts Nested Model Form.
thanks Ryan Bates great work.
http://apidock.com/rails/v3.2.8/ActionView/Helpers/FormHelper/fields_for
This api dock link list many Nested Attributes Examples including one-to-one, one-to-many. It's very helpful!
You can use 'accepts_nested_attributes_for' to do this; but there's a little trick in forms:
You must use the singular, and call fields_for for each profile, like this:
<% form_for #user do |f| -%>
<% #user.profiles.each do %>
<% f.fields_for :profile_attributes, profile do |ff| -%>
<% end %>
Notice that is :profile_attributes, instead of just :profile.

Resources