Render a view in a model - ruby-on-rails

I know views shouldn't be rendered from a model, but I cannot make sense of a better way to handle this problem.
I have a simple_form form that should show a list of object as checkboxes:
= f.association :results, collection: #search.results.all(order: 'cost'), as: :check_boxes
Like this it would only show the object next to a checkbox, but I need to display a bunch of more detailed information, so I'm defining the to_label method in my model like this:
class Result < ActiveRecord::Base
belongs_to :search
def to_label
"<div class='row'>
<div class='col-md-2'>#{supplier}</br><span class='label label-default'>#{brand}</span></div>
<div class='col-md-2'>#{desc}</div>
<div class='col-md-2'>#{model}</div>
<div class='col-md-1'>#{season}</div>
<div class='col-md-1'>#{qty}</div>
<div class='col-md-1'>#{expected_delivery}</div>
<div class='col-md-1'>#{msrp}</div>
<div class='col-md-1'>#{cost}</div>
</div>".html_safe
end
end
This shows some better formatted information, but still it's becoming very complicate to mantain and I will need at some point to add information like images etc. Of course it is also very wrong from a MVC point of view.
My idea is to create a partial in app/views, using haml as for the rest of my application and make my to_label method look like this:
def to_label
render "results/label"
end
Of course rails won't allow me to do that, but I have no better idea on how to implement it.

You should not access views from a model. Instead, create a helper with a function that receives the result object as a parameter (code not tested):
helpers/results_helper.rb
result_to_label(result)
render(:template =>"results/label", :layout => nil , :locals => { :result => result }).to_s
end
Then, from your view you call result_to_label(the_result) directly.
Update
In your simple_form, you can add the label_method with a lambda:
= f.association :results, collection: #search.results.all(order: 'cost'), label_method: lambda { |res| result_to_label(res) }

Bending rails that way will get you somewhere you don't wanna be. First of all you trying to render complex html layout by making simple_form thinking it's label for form control. Building custom form without use of simple_form makes more sense. Because rendering simple controls with automatic label generation is kinda simple form thing. And you are way beyond that.
So solution to your problem as it seems for me is defining to_partial_path on your class:
class Result < ActiveRecord::Base
def to_partial_path
'results/form'
end
end
and render your form with:
= render #results
it may be rendered inside of simple form you just need to build form controls with rails or simple form helpers.

Related

How to create multiple "has_many through" associations through one form?

I'm building a martial arts related database, currently I have the following associations set up:
Student has_and_belongs_to_many :styles
Style has_many :ranks
Student has_many :ranks, through: :gradings (and vice versa)
I'm generating a form as follows, depending on the student's styles:
So the headings are generated by the Style model (Tai Chi, Karate...), then their rankings listed below (taken from the Rank model), and the "Dojo" and "Date" fields should belong to the Grading model once created.
The question: I know how to build a form that creates one association (or one association + its children), but how do I build a form that creates multiple associations at once?
Also, what would be a clean way to implement the following:
Only lines which are ticked become associations
Dojo and date must be filled in for ticked lines to save successfully
If a line is unticked it will destroy any previously created associations
This is what I've currently implemented to retrieve the correct records:
class GradingsController < ApplicationController
before_filter :authenticate_sensei!
def index
#student = Student.includes(:styles).find(params[:student_id])
#ranks = Rank.for_student_styles(#student)
split_ranks_by_style
end
private
def split_ranks_by_style
#karate = #ranks.select_style("Karate")
#tai_chi = #ranks.select_style("Tai Chi")
#weaponry = #ranks.select_style("Weaponry")
end
end
# Rank model
def self.for_student_styles(student)
includes(:style).where("styles.id in (?)", student.styles.map(&:id))
end
def self.select_style(style)
all.map { |r| r if r.style.name == style }.compact
end
Complicated forms like this are best handled in a service object initiated in the primary resource's create or update action. This allows you to easily find where the logic is happening afterwards. In this case it looks like you can kick off your service object in your GradingsController. I also prefer formatting a lot of the data in the markup, to make the handling easier in the service object. This can be done a'la rails, by passing a name like "grade[style]" and "grade[rank]". This will format your params coming in as a convenient hash: {grade: {style: "karate", rank: "3"}}. That hash can be passed to your service object to be parsed through.
Without really grasping the full extent of your specific requirements, let's put together an example form:
<%= form_for :grading, url: gradings_path do |f| %>
<h1><%= #rank.name %></h1>
<%- #grades.each do |grade| %>
<div>
<%= hidden_field_tag "grade[#{grade.id}][id]", grade.id %>
<%= check_box_tag "grade[#{grade.id}][active]" %>
...
<%= text_field_tag "grade[#{grade.id}][date]" %>
</div>
<%- end %>
<%= submit_tag %>
<%- end %>
With a form like this, you get your params coming into the controller looking something like this:
"grade"=>{
"1"=>{"id"=>"1", "active"=>"1", "date"=>"2013-06-21"},
"3"=>{"id"=>"3", "date"=>"2013-07-01"}
}
Nicely formatted for us to hand off to our service object. Keeping our controller nice and clean:
class GradingsController < ApplicationController
def index
# ...
end
def create
builder = GradeBuilder.new(current_user, params['grade'])
if builder.run
redirect_to gradings_path
else
flash[:error] = 'Something went wrong!' # maybe even builder.error_message
render :action => :index
end
end
end
So now we just need to put any custom logic into our builder, I'd probably recommend just making a simple ruby class in your /lib directory. It could look something like this:
class GradeBuilder
attr_reader :data, :user
def self.initialize(user, params={})
#user = user
#data = params.values.select{|param| param['active'].present? }
end
def run
grades = data.each{|entry| build_grade(entry)}
return false if grades.empty?
end
private
def build_grade(entry)
grade = Grade.find(entry['id'])
rank = grade.rankings.create(student_id: user, date: entry['date'])
end
end
There will obviously need a lot more work to pass all the specific data you need from the form, and extra logic in the GradeBuilder to handle edge cases, but this will give you a framework to handle this problem in a maintainable and extensible way.

Rails - custom helper repeating code with fields_for

I have created a custom helper in my application.rb file, which looks like:
module ApplicationHelper
def add_feature_field(feature_type, object_form_builder, actions_visible)
object_form_builder.object.features.build
fields = object_form_builder.fields_for :features do |features_builder|
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_type, :form_actions_visible => actions_visible}
end
end
end
I am calling this helper from my view like so:
<%= add_feature_field("First Name", customer, false) %>
<%= add_feature_field("Last Name", customer, false) %>
<%= add_feature_field("Date of Birth", customer, false) %>
This is working pretty much as anticipated, except for one major hurdle: the second time the helper is called, it renders 2 fields instead of a single field, and the third time it renders 3 fields.
I assume that what is happening is that the fields_for loop in my helper is picking up the previously built objects, and also rendering those - can anyone suggest a way of preventing this?
EDIT: For clarity, as per the comments, this helper method is being used within the Customer form; the Features being created are nested attributes.

Populate a form with the new()-method

Problem solved. HTML5 localStorage messed with me.
I'm trying to populate a form with parameters from the new()-method, and I can't get it to work.
Every user has default values for the form saved in the database(in a table called defaults), and when you create a new record I want it to be populated with the values from that table.
#default = Default.find_by_user_id(current_user.id)
#invoice = Invoice.new(:title => #default.title, :company_information => #default.company_information)
render 'create'
and then in my view:
form_for #invoice, :url => { :action => "create"} do |f| ...
What happens is that the values that are default for invoice are created, but not the ones created in the new()-method.
The weirdest part is that when I check the source code after the page is loaded, the inputs value attributes is filled with the correct information, but not rendered on the page...
What you're doing here:
Invoice.new(:title => #default.title, :company_information => #default.company_information)
Makes sense and should work…unless those fields are protected from mass assignment.
class Invoice << ActiveRecord::Base
attr_accessible :some, :other, :fields
...
end
This would allow you to set :some, :other, (and) :fields when you initialize your Invoice object, but it will prevent you from setting any other "attributes".
Strange, I don't see anything wrong with what you are trying to do... maybe something on the browser side (javascript, css, etc) is fowling things up?
Check to see if there is something selectable inside the form inputs or try creating a vanilla form without any javascript or css. Or, you might even try simply printing the contents of the attribute in the html (without using input/textarea tags) using something like:
<%= #invoice.title %>
This will at least help confirm that the default values where indeed set. Additionally, using:
<%= f.object.title %> # place me inside the form_for block
will help you confirm that the form builder instance also has the correct value.
Good luck.

Rails - default value in text_field but only for new_record?

On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.

What is the elegant solution for unrelated views in MVC web frameworks?

I've had a problem with the following issue in Rails and ASP.Net MVC. Often there are multiple widgets of functionality on a page, yet one controller action is supposed to render the page. Let me illustrate:
Let's say I have a normal e-commerce site, and the menu is made of categories, while the page is to display an group of products.
For the products, let's say I have an action on a controller that looks something like:
def product_list
#products = Products.find_by_category(:name => 'lawnmowers')
end
And I have a layout with something like
<div id="menu"><%= render :partial => 'menu' %></div>
<div id="content"><%= yield %></div>
The products have a view...
<%= render :partial => 'product', :collection => #products %>
(note I've ommited the product view as irrelevant)
And the menu has a partial...
<% Category.each {|c| %>
<%= render :partial => 'menu_node', :locals => { :category => c } %>
<% } %>
The line I have a problem with is the "Category.each.do" in the view. I'm fetching data in the view, as opposed to using variables that were set and bound in the controller. And it could easily be a more complex method call that produces the menu.
The solutions I've considered are:
-A view model base class that knows how to get various pieces of data. But you could end up with one of these for each conceptual "section" of the site.
-a local variable that populates at the top of each method (violates DRY)
-the same thing, but in a before_filter call
None of these seem very elegant to me. I can't help but look at this problem and think that a MVP presenter per view (not screen) is a more elegant solution.
ASP.Net MVC has render action (different from rails render :action), which does address this, but I'm not sure what I think of that solution.
Thoughts? Solution suggestions?
Added Note:
The answers provided so far are good suggestions. And they apply to the example I gave, where a menu is likely present in every layout, and is clearly secondary to the product data.
However, what if there is clearly no second class citizen? Portal type sites commonly have multiple unrelated widgets, in which each is important.
For example, What if this page was displaying weather trends, with widgets for temperature, humidity, and precipitation (and each is a different model and view type).
In rails we like to have a concept of thin-controllers, thick-models. So I think you're right to not want to have variables set in the controller.
Also, in order to enable a more-complex method later on, I recommend doing something like:
/app/controllers/application_controller.rb
before_filter :add_menu_nodes
def add_menu_nodes
#menu_nodes = Category.menu_nodes(current_user)
end
/app/views/layouts/application.html.erb
<%= render :partial=>:menu, :locals=>{:categories=>#menu_nodes} %>
/app/models/category.rb
def self.menu_nodes(current_user)
Category.all.order(:name)
end
That way in the future you could update Category.menu_nodes with a more complicated solution, based on the current user, if you need.
Forgive me if I butcher the Ruby (or misunderstand your question), but what's wrong with
class section_helper
def menu( section )
// ...
menuBuiltAbove
end
end
in the view
<%= section_helper.menu( 'section' ) %>
?

Resources