Rails 3 views refactoring: conditionals and finds - ruby-on-rails

What would you recommend as the best way to refactor these two bits of view code?
<%if Supplydetail.find_all_by_isbn_id(#isbn).first.nil? %>
<%else%>
<%if Productavailability.find_by_code(Supplydetail.find_all_by_isbn_id(#isbn).first.productsupply_supplydetail_productavailability).nil? %>
<%else%>
<li><%= Productavailability.find_by_code(Supplydetail.find_all_by_isbn_id(#isbn).first.productsupply_supplydetail_productavailability).value %></li>
<%end%>
<%end%>
and (using formtastic)
%li.tip
= tooltip(:test, :hover)
= f.input :relatedmaterial_relatedproduct_idvalue, :label => "Related ISBN", :as => :select, :collection => Isbn.all, :label_method => :descriptivedetail_titledetail_titleelement_titlewithoutprefix, :value_method => :productidentifier_idvalue
%li.list
= link_to "Edit list", isbns_path
I have examples of each of these about a bazillion times in my app, and would like to know I'm refactoring in the best way before I dive in to this rather huge job.

First of all, an empty if branch in an if/else usually (but not always!) smells bad so don't do that, it just makes your code harder to read and understand.
Also, you're computing Supplydetail.find_all_by_isbn_id(#isbn) and Productavailability.find_by_code(...) twice just to get your <li> output. Don't do that either.
And you might want to push most of that logic into your controller (or maybe a helper depending on where and how often it is used) to cut down on the ERB-noise.
Maybe something like this would serve you (and whoever gets to maintain your code) better; a bit of controller stuff first:
#avail = nil
by_isbn = Supplydetail.find_all_by_isbn_id(#isbn).first
if by_isbn
#avail = Productavailability.find_by_code(by_isbn.productsupply_supplydetail_productavailability)
end
And then in your ERB:
<% if #avail %>
<li><%= #avail.value %></li>
<% end %>
If you're doing a lot of this sort of thing then you could add a Productavailability.for_isbn convenience class method in your model. Then your controller would just need:
#avail = Productavailability.for_isbn(#isbn)
But I wouldn't worry about it until you start repeating yourself.
I'm not familiar with formtastic so I can't help you with that.

Related

Rails: Nested form with STI inheritance informing forms, how to make work with AJAX?

Okay, first and foremost, I am doing something very complicated. It's possible I've gone in a wrong direction.
What I currently have is a STI inheritance model, StateDescription. Subclasses of StateDescription describe a specific state I could care about, such as "LocationHasItem" or "ItemNearOtherItem" or what not.
I understand that I may eventually want to upgrade this to a multi-table inheritance model, as not all subclasses of StateDescription use all possible variables (though there is a high degree of overlap).
These StateDescriptions are owned by another class in a "has_many/belongs_to" relationship.
So, inside the form for this other class, I have:
<%= f.fields_for :conditions do |e| %>
<br>
<%= render :partial =>"/state_descriptions/form", :locals => {:e => e, :universe => #story_section.universe, :div_id => "condition"}%>
<Br>
<% end %>
The StateDescription itself checks for which type it should render, then renders the appropriate partial like so (only showing one subclass, for clarity's sake):
<div id="<%=div_id%><%=e.object.id%>">
<li>
<%= e.select(:type, StateDescription.subclasses.collect{|x| x.to_s}) %>
<br>
<%= e.label "Inverted?" %>
<%= e.check_box :invert %>
<Br>
<% if e.object.type.to_s == "StateDescription::ItNear" %>
<%= render :partial =>"/state_description/it_nears/form", :locals => {:e => e, :universe => universe, :div_id => div_id}%>
<% end %>
<% end %>
</li>
</div>
The subclass partial looks like:
<%=e.collection_select 'item_id', universe.items, :id, :title%>
<%= e.object.title_middle_fragment%>
<%=e.collection_select 'item2_id',universe.items, :id, :title%>
Thus the form starts out the same for all subclasses, and only differs as required.
This works PERFECTLY and I was very happy with it...until I decided I wanted to have ajax update the webpage with the correct form when the sublclass is chosen out of select input field.
Then I realized I couldn't pass the form helper reference ("e" in this case) to the partial from a controller.
IS there a best practice for this case, or I am just doing something so complicated I should do straight jQuery or something and leave rails out of it?
If I put the entire form (including the things each subclass has in common) in each subclasses form, that doesn't seem very dry. Not only that, I'm not sure that I would be able to then associate the StateDescription subclass back up to the parent class...
But, if I do it AJAX, I suppose I could not worry about nested forms and just have the parent be a hidden field or something, and have the StateDescriptions save on their own through AJAX?
Would this be the best solution (if it even works?) or is there some simple Rails way that I am missing?
Well, I still don't know if this is the easiest, or DRYist way to do it, but I ended up putting the whole form in the partial, and then just not bothering with nested forms. This worked just fine with the AJAX solution.
It makes me sad to repeat the form elements they have in common every time, but it's worth it for the AJAX functionality.
I'll try making a bit more DRY, it occurs to me I could try making the subclass partial not need a form, but it could still call a super class form partial to insert the elements each subclass has in common.
I had a similar problem with STI model, which is owned by another class in a "has_many/belongs_to" relationship. In order to build the form for this other class dynamically, I used cocoon gem, together with simple_form.
Cocoon adds methods that dynamically add/remove fields for each concrete class, that inherits from your base STI class.
For example (Publication is the owner class, Item is the base STI model, and Post, Video and Image inherit from it):
# _form.html.haml
= simple_form_for #publication, :html => { :multipart => true } do |f|
= f.simple_fields_for :items do |item|
= render 'item_fields', :f => item
= link_to_add_association 'Add a Post', f, :items, :wrap_object => Proc.new { |item| item = Item.new }
= link_to_add_association 'Add an Image', f, :items, :wrap_object => Proc.new { |item| item = Image.new }
= link_to_add_association 'Add a Video', f, :items, :wrap_object => Proc.new { |item| item = Video.new }
= f.button :submit, :disable_with => 'Please wait ...', :class => "btn btn-primary", :value => 'Save'
The :wrap_object proc passes the concrete class to item_fields which renders the correct partials, such as image_fields or video_fields or whatever.
I hope this helps.
I wrote a longer explanation for this problem at: http://www.powpark.com/blog/programming/2014/05/07/rails_nested_forms_for_single_table_inheritance_associations

Draper with form_for in method

I am trying to create a method in a draper decorator that will spit out a form_for. I have a form that i'm using for searching records on the index view and have dozens of resources, so I really want to maintain this logic in one place if possible (application decorator). My problem is that I have no idea how to render a form_for in the decorator. I've come across some uses of concat and capture to try and accomplish this, with no luck. All i've been able to get is a couple of my divs to display (not as html either, just as plain text). Any ideas on how I can do this? This is the code i've got:
def crud_index_search(search_obj)
h.concat "<div id='basic_search' style='display:none;'>"
search_form_for search_obj do |f|
h.concat '<div class="input-append">'
f.text_field :name_or_description_cont, :placeholder => 'Quick search ...', :id => 'search'
h.concat "<button class='btn'><i class='icon-search'></i></button>"
h.concat '</div>'
link_to 'Advanced Search', '#', :id => 'advanced_search_btn', :class => 'pull-right'
end
h.concat '</div>'
h.concat "<div id='advanced_search' style='display:none;'>"
search_form_for search_obj do |f|
f.condition_fields do |c|
h.concat render "crud/partials/condition_fields", :f => c
end
h.concat "<p>#{ link_to_add_fields 'Add Conditions', f, :condition }</p>"
f.submit 'Search'
h.concat "#{link_to 'Basic Search', '#', :id => 'basic_search_btn', :class => 'pull-right'}"
end
h.concat '</div>'
end
and in my view...
<%= #categories.crud_index_search #search %>
Any help would be greatly appreciated!
FYI, I've already put this into a partial and that works, however there is some more complex logic I will need to add to this that will make it differ on a per resource basis, so a partial is not ideal for me.
Thanks
Draper is excellent for putting logic pertaining to the view and does well at simple presentation, however you should in principle be leaving the content presentation to the view since that is what it is there for.
A pattern that I recommend is using the method on your Draper object to facilitate the logic and for each logic path render an appropriate partial.
So you might have something like this:
def crud_search_index(search_object)
if search_object.something_to_check
h.render 'shared/one_version_of_my_form'
else
h.render 'shared/another_version_of_my_form'
end
end
And, even better, since your form is directly related to that search_object, I would actually create a Draper Decorator for that search object and put the method to generate the form on there instead of an "application" decorator.
Then your view is something like:
<%= #search.form %>
And if you need a reference to the #categories then pass it in to that method.

Passing a parameter between views in Rails

I am trying to accomplish something like this:
I am creating a simple blog. I have set up categories for my blog.
I want that when my user goes to posts/index, he sees a list of all categories.
Example:
Text
Image
Upon clicking on a category, my user gets redirected to the posts/new page, where the category_id field will by transmitted through a hidden_field.
So my code right now is:
in posts/index
<% #categories.each do |c| %>
<%= link_to c.name, new_post_path(:category => c.id) %><br />
<% end %>
and in my posts/_form i'm trying to do something like this
<%= f.hidden_field :category_id, :value => params[:category_id] %>
which is not working though, because the html output is
No value is being passed.
What is the correct way to proceed here?
Thx!
At first glance it looks like a simple mistake mixing up the param names category and category_id.
Try this:
<% #categories.each do |c| %>
<%= link_to c.name, new_post_path(:category_id => c.id) %><br />
<% end %>
Also, from what i can understand in your code, it seems a post belongs to a category. In such case, you could nest routes from one in another, and paths for creating nested object would become accessible, such as new_category_post(#category).
The routing would look like that:
resources :categories do
resources :posts
end
You can read about this matter here: http://guides.rubyonrails.org/routing.html

future proofing rails partials calling scheme

I'm porting a php app to rails and am wondering what the best practice for having the most flexibility in a rails partial is. I saw a lot of "you could do it like this"
I'm thinking like this:
<%=render :partial => 'shared/show_info',
:locals => {
:info => x ,
:hide_location => true} %>
where use a :locals even if only one argument.
Any ideas on whether this would be the best way for maximum scalability in the future or why this would not be a good idea?
thx
edit #1
added the shared/_show_info.html.erb - this would be the type of thing I'd be doing :hide_location which takes out the location
<% if !local_assigns.has_key? :hide_location %>
<%=info[:location] %>
<% end %>

Building a Helper to Output buttons

I'd like to output a form button:
<% content_tag :button :type => :submit, :class => :positive do %>
<%= image_tag "icons/tick.png"%>
Save
<% end %>
Which should generate:
<button type="submit" class="positive">
<img src="/images/icons/tick.png" alt=""/>
Save
</button>
I have this throughout my site, it's getting messy and I just learned that Rails has helper methods. I was curious. Would it be possible to build a helper method so I could just say something like this in rails
<%= form_button(submit) %>
What do you think? Would this live in the application_helper.rb file?
Something like:
def form_button (type)
if type == 'submit'
<% content_tag :button :type => :submit, :class => :positive do %>
<%= image_tag "icons/tick.png"%>
Save
<% end %>
end
end
To answer your first question: Yes it is possible and that is what the Rails framework encourages: reuse of code (otherwise known as the DRY-principle). Specific to view logic, there is a helper for each and every one of your models. This is the default/convention, though it is not something you have to adhere to. I'll explain more about this in the next paragraph. So yes, put your form_button method into a helper -- not necessarily application_helper.rb.
To answer your second question: You could stick it in application_helper.rb, but there's nothing stopping from you making things a little more logical (i.e., creating a buttons_helper.rb). In Rails 3 specifically, all helpers are available to each and every view (though this has ruffled some feathers). So what you could do instead is create a new helper for yourself (i.e., rails g helper Buttons), specifically for creating your buttons, and put your logic in there.
Take a look at this Railscast. It describes in more detail exactly what I've said above. I had the same question and found it very helpful. http://railscasts.com/episodes/101-refactoring-out-helper-object
If the helper is going to be all around the site then application is probably the best place to put it for helpers.
You could do what you have, you don't need to put the <%%> tags since those are for erb Embeded Ruby which are your view extensions and helpers are just .rb so there is not need for that.
I checked the API and it looks like you don't need to role your own helper for this:
content_tag(:p, "Hello world!")
# => <p>Hello world!</p>
content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
# => <div class="strong"><p>Hello world!</p></div>
content_tag("select", options, :multiple => true)
# => <select multiple="multiple">...options...</select>
A better way to do this is apply the image as a background image in you stylesheet or do it inline. You don't need to make a helper for this and BTW what happens if the type is not 'submit'? Do you have other inputs to check for or a default? If not it doesn't make sense to wrap it in a helper.
so you can do this"
<% submit_tag "Save", :class => "your_class", :style => "font-size:10px;"%>
or
<% f.submit "Save", :class => "your_class", :style => "font-size:10px;"%>

Resources