I am using a namespaced Presenter object to help refactor some view presentation logic for my model attributes.
For one object being sent from the controller I would do
#user = Users::UserPresenter.new(#user)
and that works fine. For a query of users, I created a .present() method that maps and applies the UserPresenter.new to each user, so I do
#users = Users::UserPresenter.present(users)
and that works fine. But what about when I am passing an object that then iterates through a relationship in the view. A simple example would be
<% appointment.users.each do |user| %>
<% user = Users::UserPresenter.new(user) %>
<li> <%= user.age%></li>
<% end %>
A more complex example would be
<% appointment.appointment_host.family.users.each do |user| %>
<% user = Users::UserPresenter.new(user) %>
<li> <%= user.age%></li>
<% end %>
user_presenter.rb
module Users
class UserPresenter < SimpleDelegator
# methods
end
end
I don't like having to set the Presenter object in the view. What is a better way to handle this? Ideally using similar patterns as I have so far.
Perhaps you could create a hierarchy of presenters similar to model associations, and then pass only the root presenters to the view. Something like this:
class AppointmentPresenter
def initialize(appointment)
#appointment = appointment
end
def users
Users::UserPresenter.present(#appointment.users)
end
def host_family_users
Users::UserPresenter.present(#appointment.appointment_host.family.users)
end
# or perhaps even indeed create a presenter for each collection:
def appointment_host
AppointmentHostPresenter.new(#appointment.appointment_host)
# this presenter would have the `family` method returning a FamilyPresenter, etc.
end
end
I.e. some kind of "decorators" for the model associations, returning presenters instead of model objects.
Related
I have the following code in a Rails partial being used in some mailers but am not happy with my solution and have the feeling this is far from optimal.
I have an email which
From my mailer:
def the_email_i_am_sending(user, inquiry, params = {})
get_variables(inquiry) #This also provides access to my `#user` object
#contact_name = [params[:guest_last_name].to_s, " ", params[:guest_first_name].to_s].join
I always have #user but on occasion a specific partner will call our API with additional params of [:guest_last_name] and [:guest_first_name] as defined above. This allows me to define #contact_name as a separate instance variable.
When this is .present? i.e. not nil, I want to render #contact_name in a field on the email rather than the #user.login that would pull from our DB.
My mailer view then uses the following code to decide which partial it will render.
<% if #contact_name.present? %>
<%= render 'meet_your_guest_v3', tujia_guest: #contact_name %>
<% else %>
<%= render 'meet_your_guest_v3' %>
<% end %>
My solution is then to utilise this code in the partial being rendered in the mailer. It seems a little verbose but I am unsure about the correct usage of local_assigns.has_key?
<% if local_assigns.has_key?(:partner_guest) %>
<%= partner_guest %> <p>(via our partner</p>
<% else %>
<%= #user.login %>
<% end %>
Is there a better way?
You should definitely follow the advice from #Jon regarding dealing with params in your controller/mailer. Additionally you should just pass #contact_name every time to the underlying partial, regardless if it is present or not, then check only where you want to render it, if it is present. This way you would skip one conditional:
#email_view.html.erb
render 'meet_your_guest_v3', parnter_guest: #contact_name
_contact_name.html.erb
<% partner_guest.present? %>
...
A further step could be using a special decorator object, which would deal with the presentation logick. It would check wether contact_name was provided from outside or from the model and render the desired html tag for the contact_name (or it could just return it as string). See following pseudocode using the draper gem:
class MyController < ApplicationController
def send_mail
#user = User.find(...).decorate(
contact_name: [params[:guest_last_name].to_s, " ", params[:guest_first_name].to_s].join
)
MyMailer.the_email_i_am_sending(#user)
end
end
class MyMailer < ApplicationMailer
def the_email_i_am_sending(user)
#user = user
mail(to: ..., subject: ...)
end
end
class UserDecorator < Draper::Decorator
def contact_name_tag
if (contact_name.present?)
h.content_tag(:div, contact_name)
else
h.content_tag(:div, user_name)
end
end
end
#email_view.html.erb
<%= #user.contact_name_tag %>
However if the presentation logic isn't very complicated, going with a couple conditionals and perhaps extracting them into basic rails helpers is fine and using a presenter may be an overkill
I have nested resources with a parent model called main and a child model called temperature. Each main can have many temperatures (1 to many). Is there any way to post multiple temperatures, with each temperature going to a different main object, on one single form using form_for and fields_for (I would like to have just one submit button)? The following is my main's index view and main & temperature controllers. Thanks!
<% #mains.each do |main| %>
<tr>
<td>stuff.....</td>
<td><%= form_for main do |f| %>
<%= f.fields_for :temperature do |t| %>
temp: <%= t.text_field :temp %>
<% end %>
<% end %></td>
</tr>
class MainsController < ApplicationController
#stuff
def index
#mains = Main.all
end
private
def main_params
params.require(:main).permit(:freezer_id, :freezer_name, temperatures_attributes: [:id, :main_id, :temp, :date])
end
end
class TemperaturesController < ApplicationController
#stuff
def create
#main = Main.find(params[:main_id])
logger.debug 'testing 1'
#temp = #main.temperatures.create(temperature_params)
if #temp.save
redirect_to main_path(#main)
else
render 'mains/show'
end
end
private
def temperature_params
params.require(:temperature).permit(:temp, :date)
end
end
What you are trying to do is a bulk update/create on the database for diferent mains. Rails can't handle this on his convention as long as the require(param) is acting. You will need to create a bulk handler in the controller to create individual hashes from each entitiy in the array and then create a handler for the response of each model validations.
Not easy to do, but possible.
Some references, for APIS, nothing for forms, but as long as the view create the appropiate hash, you can reuse it.
http://www.l1ghtm4n.com/post/53259404576/patterns-for-rest-api-bulk-operations
https://github.com/arsduo/batch_api
http://forrst.com/posts/Run_any_rails_RESTful_action_more_than_once_bul-Yb5
To generalize my problem, I am using an API that returns an iterable object. Within that those is an id for each object. My controller looks like this:
class SearchController < ApplicationController
def index
#search = API.find(params[:query])
end
end
My view is something like this:
<% #search.each do |thing| %>
<h2><%= thing.attr2 if thing.attr1 %></h2>
<%= API.list(thing.attr2) %>
<% end %>
I have tried adding a method into
class SearchController < ApplicationController
def index
#search = API.find(params[:query])
def getList(attr2)
API.list(thing.attr2)
end
end
end
and adding index and self before the definition (ex: self.getList(attr2)) and calling it in all those variations in the view:
<%= getList(thing.attr2) %>
I am wondering where I am going wrong here. I have additionally tried to add in the helper_method line as I read in a few docs but it would not recognize it. Also, would this be the correct way to go about this style-wise? Having a hard time finding references for it makes me think this isn't standard practice.
The method I was trying to make is a helper method, and therefore, needs to go in the helper method for the controller.
I believe all of the following break the MVC paradigm but wanted to double check if this was the case. In all cases the view is directly accessing data rather than having the data being passed in. From my understanding of MVC, it should never do that. The controller should get all the data that is necessary to render the view as to not couple the view and model directly. Is my understanding correct?
Accessing the database through a view helper
# in app/helpers/view_helper.hrb
def some_view_helper(person_id)
#person = Person.find(person_id)
end
Accessing another web server through a view helper
# in app/helpers/view_helper.hrb
def another_view_helper(person_id)
# makes http request over the wire to get json back
#json = WebService.get_person(person_id)
end
Accessing the database through a view model
# in apps/controller/person_controller.rb
def show
#person = Person.find(params[:id])
#page_model = PageModel.new(#person)
end
#in app/views/persons/show.html.erb
<% #page_model.friends.each do |friend| %>
...
<% end %>
#in app/models/person.rb
class Person < ActiveRecord::Base
has_many :friends
end
#in app/models/page_models/page_model.rb
def initialize(person)
#person = person
end
def friends
#person.friends
end
Accessing web server to get data through a view model
# in apps/controller/person_controller.rb
def show
#person = Person.find(params[:id])
#page_model = PageModel.new(#person)
end
#in app/views/persons/show.html.erb
<% #page_model.friends.each do |friend| %>
...
<% end %>
#in app/models/page_models/page_model.rb
def initialize(person)
#person = person
end
def friends
WebService.get_friends_for_person(person_id)
end
For 1 and 2, you could just set an instance variable (#person) in the controller.
For 3, your view code isn't so bad, but why have a separate page model? You can also load the friends up front in the controller:
# in apps/controller/person_controller.rb
def show
#person = Person.find(params[:id], :include => :friends)
#friends = #person.friends
end
Example 4 is a bit worse, since you're doing external web service calls in a view. Don't do that.
This article has a good example of what an ideal clean view would look like: http://warpspire.com/posts/mustache-style-erb/
I have a Project Index View that shows all the projects in an app
I want that view to show if the user signed in is a member or not....
In the Project Index View I have:
<% if teammember? %>
<td>Request to Join</td>
<% else %>
<td>Already Joined</td>
<% end %>
Then in the project's controller I have
def teammember(projectid)
do some stuff.....
end
But this gives me a "undefined method `teammember?"
You don't include the teammember method in the controller, you put that in the helper file (app/helpers/project_helper.rb)
module ProjectHelper
def team_member?(project_id)
# include other logic here
true
end
end
Then in any view that your Project controller renders, you can do:
<% if team_member?(project.id) %>
This is a team member.
<% else %>
This isn't a team member.
<% end %>
If this is a controller method that you need to access in the view, you can make it available like this:
class ProjectsController < ActionController::Base
helper_method :team_member?
end
This is essentially the same as if you had defined the method in helpers/projects_helper.rb
Just make sure you call the methods the same: your example shows one with a question mark, and one without.