Form has_many in text_area - ruby-on-rails

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.

Related

Complex Rails Form Best Practices

I've got a relatively complex form I'm trying to code efficiently. Most online examples of nested forms deal with very clear hierarchical relationships, mine does not.
Below is the data model. The essential job of the form is to create a "Job Entry" record while at the same time creating a new "Entity" record - which is a person. Several relationships come to bear in this form.
A "Job" is already created. The Job has 1-to-many "Questions" which exist before the user hits this form. However, they must fill in "Answers" to the questions. They also choose one of many pre-created "Job Roles".
The question is how to leverage "form_with" and "fields_for" for all these inter-related models.
My assumption is to ditch built-in helpers and just use a form_tag and roll everything together manually. But maybe there is a "correct" way to roll forms that do not necessarily abide by parent-child relationships? In my example, there is no pure top-level object to start with since many child objects already have records, but maybe I am wrong and Entity should be the starting point?
Entity has_many Job_Roles
Entity has_many Job_Entries
Job has many Job_Roles
Job has_many Job_Entries
Job has_many Questions
Question has many Answers
Answers belong_to Entity
Agency has_many Job_Entries
etc...
There is no need to ditch the built-in helper: Rails has thought about that, it's called nested forms.
Here is an example:
<%= form_with model: #job do |f| %>
Job entries:
<ul>
<%= f.fields_for :job_entries do |je_form| %>
<li>
<%= je_form.label :kind %>
<%= je_form.text_field :kind %>
<%= je_form.label :street %>
<%= je_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
You can nest as many children forms as you'd like using fields_for. Don't forget to use accepts_nested_attributes_for in the parent models.
Nested forms as Mike proposed are a rails-way solution of your problem. It is ok - but for complex forms, with lot of validations, it may not be the best solution). You could consider using a FormObject pattern instead.
FormObject is a simple ruby class that uou can keep it i.e. in Forms folder and use as below:
class JobEntryForm
include ActiveModel::Model
attr_accessor :customer_id, :agency_id, :name, :question_text #you can use atributes from different models
validates :customer_id, presence: true #you can validate yu attributes as you want - your in necessity to use model validation
def initialize(attributes:)
#customer_id = attributes[:customer_id]
#agency_id = attributes[:agency_id]
#name = attributes[:name]
#question_text = attributes[:question_text]
end
#implement whatever you need
end
than in you controller:
#form = JobEntryForm.new
and you your view:
<%= form_for #form do |f| %>
<%= f.label :customer_id, 'Customer' %>:
<%= f.text_field :customer_id %>
...
<%= f.submit %>
<% end %>
And - at the end - in your controller create method:
def create
#form = CreateJobEntry.new.call(attributes: form_params) #service object to keep your controller clean.
end

How to create a new category within the post form?

I need to create a new blog category in the blog post form only when the new blog post does not match the listed categories. I am creating two form_for methods in rails but it shows errors.
How to implement this on ruby on rails.
I am expecting the output as, if we are creating a blog post, suppose our post matching category is not listed in the select. we need need to create a new category at the same time.
You're probably looking for accepts_nested_attributes_for. If you add accepts_nested_attributes_for :category in your Post model, you'll be able to use fields_for in your post form. You didn't paste any code so this may not work without some adjustments for your specific Rails app, but you'd generally end up with a form like this:
<%= form_for #post do |post_form| %>
Title: <%= post_form.text_field :title %>
(...other post fields here...)
<%= fields_for :category, #post.category do |category_fields| %>
Category Title: <%= category_fields.text_field :title %>
<% end %>
<%= post_form.submit %>
<% end %>
Depending on your use case you may want to set #post.category to Category.new in your controller, and you'll want to whitelist the nested parameters in your controller if you're using strong params. See Nested Forms on Rails Guides for an example of this. You'll also want handle selecting an existing category separately from this.

Rails form for mailing

I'm trying to build a form which preloads content from two models (two variables being passed, being shown in the textfields) and then, not saves the data but sends the altered content (from the textfields) as two(?) variables to a mailer class.
I've managed to preload the data from one of the two models but am not sure how the form_for tag has to look like to get both models loaded as well as targeting the mailer class method instead of updating the model entity when pressing "send".
Do I need the accepts_nested_attributes_for attribute inside the model if I'm not saving anything?
I hope someone could give me an small example of the crucial parts. A thousand thanks!
You can use fields_for to include other models in same form. You can use it inside the same form_for what is present.
Checkout the example here from the api docs,
<%= form_for #person do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
<%= fields_for #person.permission do |permission_fields| %>
Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<%= f.submit %>
<% end %>
when you submit the data from this form, you can just use that data to pass to the mailer class from controller. UserMailer.get_user_info(params[:name], params[:address]).send
Creates a scope around a specific model object like #form_for, but doesn't create the form tags themselves. This makes #fields_for suitable for specifying additional model objects in the same form.
Refer Docs here:.
fields_for(record_name, record_object = nil, options = {}, &block)

How to access nested child element in fields_for

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.

Add star ratings to my model rails3

I have a model called teacher that I'd like to add ratings to (5 star). Currently, I implement this by adding a ratings nested route (resource rating) inside of my teacher resource. Then I created a model: rating with (id, user_id, teacher_id, ratings, ...). Then I created a form with hidden fields, one of which is called stars. When a user clicks on a star, I use jQuery to send an AJAX request to create/update the rating for that user and teacher.
My confusion is this: I'm having two separate forms on the page. I have a form for writing the reviewers comments. This form has two fields: title, comments (and submit). Then I have the ratings form with hidden fields. Is this the right way to go about something like this? It seems to me that I should really have the ratings model fields somehow embedded in the main review form.
Any help highly appreciated. Thank you.
[EDIT]
I've updated my application so that instead of rating a teacher object, users now rate a comment on a teacher
my setup is something like this:
routes
resources :comments as :teacher_comments do
resource :rating
end
models
comment
has_one :rating
attr_accessible :body, :rating_attributes
accepts_nested_attributes_for :rating
rating
belongs_to :comment
attr_accessible :stars, :user_id, :teacher_id, :comment_id
view
<%= form_for( #comment, :remote => true, :url => teacher_comments_path ) do |tc| %>
<%= tc.text_area :body, :maxlength => 450 %>
<%= tc.fields_for :rating do |builder| %>
<%= builder.text_field :stars %>
<% end %>
<% end %>
I don't see the text_field for the stars. It's just not showing up. Is there something I missed?
Indeed, it's generally better to have all these fields in a single form (good for user experience).
Edit:
You might use the method accepts_nested_attributes_for (as you suggested in the comments below). Put the following in your parent Model (teacher); then you should be able to create a single form to handle inputs for both Models:
in the model:
class Comment < ActiveRecord::Base
has_one :rating
accepts_nested_attributes_for :rating
end
in the controller:
def new
#comment = Comment.new
#comment.rating = Rating.new
end
Ryan Bates gives a detailed screencast on the use of these concepts here: Nested Model Form. I recommend it for users who want to know more ins and outs.
Original:
This means that you'll need to point the form to an action that can handle both types of input. You can still use form_for if you like, but specify an action other than your default (or change the code within the default action in your teacher_controller.rb file):
<%= form_for #teacher, :url => {:action => 'create_and_rate'} do |f| %>
Since rating is a Model distinct from teacher (whose form we just created), you'll want to use the generic _tag form helpers for the rating fields.
<%= text_field_tag :rating, :name %> # rating's fields should use the generic form helper
<%= f.text_field :name %> # teacher's fields can use the specific form helper
Since you are pointing to a non-RESTful action, add it to your routes file.
resources :teacher do
:collection do
post 'create_and_rate' # this will match /teachers/create_and_rate to TeachersController#create_and_rate
end
end

Resources