How to tell form_with in conjunction with STI classes? - ruby-on-rails

I have a class that I'm using STI with to provide unique, per-type logics. I do not want to create forms and controllers for each unique type, I want to leverage inheritance and have a single controller using the superclass, Package, with the subclasses not even being referenced (at this stage).
The new 'form_with' helper handles 99% of cases for me, but in this case the below code doesn't work. It throws an error because #package is a subclass of the core Package class, and that subclass doesn't have routing information. I need it to map the Package class and associated controllers instead.
<%= form_with(model: [#show, #package], local: true) do |form| %>
# Form Data Here
<% end %>

<%= form_with(#package.becomes[Package]) do |form| %>
# Form Data Here
<% end %>

This solution seems a little hackey, but it's the best I've been able to come up with. I suspect that it may be the best, absent form_with having some specific code to handle / recognize STI that I couldn't find documentation for.
<%= form_with(url: show_packages_path(#show,#package),scope: 'package' ,model: [#show, #package], local: true) do |form| %>
# Form data Here
<% end %>
By using the show_packages_path helper, I'm able to forcibly scope the route to a specific route. It's not as clean and flexible, but the extra code is there specifically to handle STI. The same for the scope key, which re-scopes the form so it doesn't try to nest all the params under the subclass name instead of the superclass name.

Related

Move logic like this to the controller or model rather than the view?

I have this logic currently in my view
<% tools_count = #job.tools.count - 1 %>
<% count = 0 %>
<% #job.tools.each do |u|%>
<%= u.name %>
<% if count != tools_count %>
<% count += 1 %>
<%= "," %>
<%end%>
<% end %>
Which just loops through some users relations and puts in a , unless it is the end of the list.
My question: This kind of logic looks really messy and clogs up my views I know there must be a better way of doing this by moving it into the controller or maybe model, does anyone know the correct way to do this kind of logic?
You can add a method like this to your Job model:
def tool_names
tools.map(&:name).join(',')
end
And use it in your view like this:
<%= #job.tool_names %>
There are couple of ways to avoid putting this kind of logic in the view layer:
Create an instance method in the model class (as spickermann suggested)
This will work for simple logic and simple projects. However, when you will want to use some helpers from ActionView::Helpers such as jobs_path or number_to_currency, a model is not a good place for it.
Create a helper method in helper modules eq. JobHelpers
Generally you can put any helper methods related to view layer in helpers. For example to share common methods for building a view components.
Use the decorator/presenter pattern and put there the view logic so model won't be polluted. Here is some more explanation about the pattern and sample implementation using draper gem: http://johnotander.com/rails/2014/03/07/decorators-on-rails/
You can do it in a single line like
<%= #job.tools.map(&:name).join(',') %>

Is a link_to a custom route or a form the best way to provide state change?

What is the 'Rails way' to provide access to methods such as the following from a view
def approve!
self.update_attribute status, 'approved'
end
Is it best to create a link to a custom route
<%= link_to 'Approve', approve_object_path(#object) %>
#objects_controller.rb
def approve
#object.approve!
end
Or to create an update form
<%= simple_form_for #object do |f| %>
<%= f.input :status, input_html { value: 'approved' }, as: :hidden %>
<%= f.submit %>
<% end %>
On the one hand, using a form and not using the approve! method at all seems to align better with restful routes.
On the other hand, a link to a custom route seems to provide less opportunity for submitted values to be manipulated by the user, and also requires less code to implement.
Which is the preferred way to do this?
I don't know if there's a preferred best practice, per se...
Just my opinion, but I normally do the link_to approach, and for an "state machine" like your example. The need for an entire form for a simple action like this is a lot of extra code that isn't necessary when an action can be called to change the state.
The counter argument to this is that it breaks CRUD, and requires a non-CRUD route. Convention over configuration champions would probably prefer an entire new controller to change the state of the object.
TL;DR - I do the link_to approach, and I use :remote => true to make it asynchronous so the page doesn't even reload (unless you need the page to redirect elsewhere).
You can change state remotely with both the scenarios.
But I think if only a state has to be changed then use link_to. As we don't need to have form features with listed attributes in params here.

What's the difference between using nested routes vs. accepts_nested_attributes_for?

I may be confusing the two entirely, but I'm seeing that forms can facilitate associations using an array argument based on nested routes, ex:
<%= form_for [#project, #task]...
or using the fields_for helper if the parent class accepts_nested_nested_attributes_for the child.
What's the difference / trade-offs between these approaches?
I didn't find the answers given to be as clear as I had hoped, so after doing a bit more research I discovered an answer that satisfied me and so I figured I'd share it with others.
Nested routes approach
Basically, the nested routes approach is useful when you're presenting a form for a child model as a single form in it's own right. In other words, if you have a blog with a Post model with a Comment model as it's child, you can use nested routes to present the form for the child such that submitting that form will let rails do its magic in terms of associating the child with the parent.
Nested attributes approach
The accepts_nested_attributes_for method on the other hand, is more aptly used to present a form that while taking on the appearance of a single form, is really multiple forms merged together with a single submit button.
So to summarize, the nested routes approach deals with one model in single form (albeit associated with a parent model), while the nested attribute approach deals with multiple models in a single form.
The difference might be subtle to newcomers, but meaningful enough to understand.
Hope this helps others who had questions about this. Cheers.
This is child model form:
<%= form_for [#project, #task] ... %>
where you submiting Task for the existent Project.
Here #project = Project.find(params[:project_id]) and #task = Task.new according to docs or #task = Task.find(params[:id]) if you're updating existing task.
This is used in the parent model form:
<%= form_for #project do |f| %>
<%= f.fields_for :task do |builder| %>
<% # ... %>
<% end %>
<% end %>
where you can create or update both objects at once. Task attributes will be pass in the params[:project][:task_attributes] with task id if you're updating objects.

Send an extra parameter through a form in rails 3

Is there a way to send an extra parameter through a form in rails 3?
For example:
<%= form_for #post do |f| %>
<%= f.hidden_field :extraparam, :value => "22" %>
<% end %>
but lets say :extraparam isn't part of the post model..
I have an unknown attribute error in the create method of the controller when I try this, any ideas?
(I want to use the param value itself in the controller for some extra logic)
Call hidden_field_tag directly. See: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-hidden_field_tag
These helpers exist for all the major form field types, and are handy when you need to go beyond your model's methods.
The following worked for me in passing extra parameters from the view back to the controller that were a part of my model and not part of my model.
<%= hidden_field_tag :extraparam, value %>
Example usage
<%= hidden_field_tag :name, "John Smith" %>
Ya Paul is right. Hidden_field is associated with your model whereas the extra _tag fields are not. I'm not sure of your needs but It's generally recommended in the RoR community to avoid passing a ton of hidden_fields like you might do in a php application.
Ive seen some code where ids were getting passed around in hidden fields which rails takes care on its own if you know the best practices and take full advantage of the framework. Of course I'm just saying this as general info as there are sometimes better ways at accomplishing the same functionality. Good luck on your apps.

Rails: Fields_for without a scope?

Is there a way to use fields_for with in a form without having a scope?
For example:
<% fields_for "user[]" do |x|
<%= x.text_field :name %>
<% end %>
Without the user model being loaded in memory?
I got it working using territory[user][][name], but I would like to keep it in ERB.
I think the answer would be 'no', since those form_for and fields_for would try to determine default value from that given instance variable.
However, I think if you want to lower memory usage from loading that model, you might try to create a mock-up model to return nil values, and create a instance object from that one instead.
is there any specific reason you need to use form_for specifically? Its really designed to be used with an instantiated model object.
Alternatively, why don't you just use the regular form helper tags. You can define it as follows:
<%form_tag :my_form do %>
<%= text_field_tag :foo, :bar %>
<%end%>
You may want to check the documentation for action view to see how it all works.

Resources