Many presenters, have something of the form of
class MyClassPresenter < SimpleDelegator
def something_extra
end
end
view_obj = MyClassPresenter.new my_class_instance
I want to transverse the instance:
view_obj.nested_obj.nested_obj.value
This means creating multiple presentation objects, which in effect start just copying the models.
class MyClassPresenter < SimpleDelegator
def something_extra
end
def nest_obj
AnotherPresenter.new(__get_obj__.nest_obj)
end
end
To demonstrate a real world example a bit better
class UserPresenter < SimpleDelegator
def home_page_url
"/home/#{__get_obj__.id}"
end
end
class MyController < ApplicationController
def show
#user = UserPresenter.new(current_user) # devise's current_user
end
end
/my/show.html.slim
/! Show profile comments
- for comment in #user.profile.comments
| Comment on:
= comment.created_at
= comment.content
The main object being passed in is #user, however, the presenter doesn't cover that far. I could create #comments, but I would like the code to be more flexible to however the front-end engineers wanted to take it.
How have other people handled multiple layers for a presenter?
Would the code look something like this? (ugh)
/! Show profile comments
- for comment in #user.profile.comments
- display_comment = CommentPresenter.new(comment)
| Comment on:
= display_comment.comment.created_at
= display_comment.content
-daniel
Related
I have read:
Concerns, Decorators, Presenters, Service Objects, Helpers, Help me Decide
and trying to figure out the difference between presenters, view objects, decorators, exhibits, and helpers.
I have multiple active record models that I need to display in a view using the show method.
Examples of what I need to display are:
ClassModule SomeTypeOfPattern
def name
User.name
end
def car_name
User.car.listing.car_name
end
def car
User.car
end
def car_marketing
User.car.marketing
end
# AND 20 to 30 other similar delegations/methods from 4 related tables
end
So if I delegate these relationships, what should the class/module be called? A presenter? Decorator? View Object? I am so confused by all these terms, but want to follow convention.
The example you are showing looks like a Presenter to me.
A presenter is an object that presents other information with its own interface.
If you changed what you have just a little, you could use it like this:
presenter
class UserCarPresenter
attr_reader :user
def initialize(user)
#user = user
end
def name
user.name
end
def car_name
user.car.listing.car_name
end
def car
user.car
end
def car_marketing
user.car.marketing
end
end
controller
class CarsController < ApplicationController
def show
#user = UserPresenter.new(user)
end
end
view
<h1><%= #user.name %></h1>
<h2><%= #user.car_name %></h1>
I have two models:
Student
Classroom
Both of them have an action that does the same exact thing: it shows a report of daily activity. That is:
/students/1
/classrooms/1
Grabs activity for the model in question and displays it on the page.
In an attempt to dry this up, I created a ReportsController which extracts all the common logic of building a report.
If I leave the routes like this:
/students/1/report
/classrooms/1/report
Then I can have the ReportsController#show action look for params for :student_id or :classroom_id to determine which model type it is dealing with (for purposes of querying the database and rendering the correct view).
But I would prefer the URLs to be cleaner, so I also changed my routes.rb file to pass the show action for these models to the reports#show controller action:
resources :students, :classrooms do
member do
get :show, to: 'reports#show'
end
end
This works, but I can no longer depend on params to identify which model to work with and which view to render.
Question: should I parse request.fullpath for the model? Or is there a better way to make a shared controller understand which model it is working with?
Routing both show methods to the same controller method for code reuse is somewhat like banging a nail in with a dumptruck.
Even if you can find the resource by looking at the request url you would start splitting the ResortsController into a bunch of ifs and switches even before you got off the ground.
One solution is to add the common action in a module:
module Reporting
extend ActiveSupport::Concern
def show
# the Student or Classroom should be available as #resource
render 'reports/show'
end
included do
before_action :find_resource, only: [:show]
end
private
def find_resource
model = self.try(:resource_class) || guess_resource_class
#resource = model.find(params[:id])
end
# This guesses the name of the resource based on the controller name.
def guess_resource_class
self.class.name[0..-11].singularize.constantize
end
end
class StudentController < ApplicationController
include Reporting
end
# Example where resource name cannot be deduced from controller
class PupilController < ApplicationController
include Reporting
private
def resource_class
Student
end
end
self.class.name[0..-11].singularize.constantize is basically how Rails uses convention over configuration to load a User automatically in your UsersController even without any code.
But the most important key to DRY controllers is to keep your controllers skinny. Most functionality can either be moved into the model layer or delegated out to service objects.
I would put the common logic in the Event Model:
#Event Model
class Event < ...
def self.your_event_method
#self here will be either student.events or classroom.events
#depending on which controller called it
end
end
class StudentsController < ...
...
def show
student = Student.find(params[:id])
student.events.your_event_method
end
end
class ClassroomsController < ...
...
def show
classroom = Classroom(params[:id])
classroom.events.your_event_method
end
end
Ok, I'm quite confused and a little stuck here, I'm trying to pass data to my Model via attr_accessor but I cant find the right way. Here is my setup so far:
class ApplicationController < ActionController::Base
before_filter :current_league
protected
def current_league
#current_league ||= Conf.all.order('updated_at ASC').last.league
end
end
class HerosController < ApplicationController
def index
#heros = Hero.all.order(:name)
end
end
class Hero < ActiveRecord::Base
attr_accessor :current_league
def some_method
puts current_league
end
end
<% #heros.each do |hero| %>
<tr>
<td><%= hero.some_method %></td>
</tr>
<% end %>
Now how do I set the #current_league inside my model? I know I can have an attr_accessor inside my model, but this only applies to an instance of this model but the index action doesn't create an instance as far as I know. Maybe someone can point me in the right direction. Thanks in advance.
The #heros variable is a collection of Hero instances. You can loop through them and set it if you want.
class HerosController < ApplicationController
def index
#heros = Hero.all.order(:name).each do |hero|
hero.current_league = current_league
end
end
end
While it works, I don't find this answer to be all that elegant. Granted, I don't know the full extend of the thing you are making, but based on the code here I would create a composite object. Something like this:
class HeroInLeague
attr_reader :league, :hero
def initialize(league, hero)
#league = league
#hero = hero
end
def some_method
# ...
end
end
Then you can create these objects inside your controller:
class HerosController < ApplicationController
def index
#heros_in_league = Hero.all.order(:name).map { |hero|
HeroInLeague.new(current_league, hero)
}
end
end
Now you've created a place for methods to go that are related the combination of heros and leagues. Why is this important? Well, with the previous approach you'd probably end up with methods on Hero that don't make any sense when there is no current league. (like the some_method method). That makes the Hero class a bit of a mess. Now you've created a place to put some_method and all its friends.
You can use delegators to make the interface of HeroInLeague a bit more friendly, so you don't have to do hero_in_league.hero.foo, but can call hero_in_league.foo directly.
I have a very big question about Rails. Suppose that we need to create a web site about blogs, we allow user registration and the user has their own management interface which make it possible for them to add, delete, edit, and select the article and comment. The operation on the article and comment might be used in other positions in the future.
So we have a article model and a comment model.
Now we create a users controller:
class UserController < ApplicationController
def articleList
end
def articleSave
end
def articleUpdate
end
def articleDestroy
end
def articleEdit
end
def articleAdd
end
def commentList
end
def commentDestroy
end
def commentEdit
end
end
But it din't look good, and when the user management control has many features, this user controller will be very big. Should I create an article controller and comment controller to process the request? Just separated into the article controller is like this:
class ArticleController < ApplicationController
def list
end
def save
end
def update
end
def destroy
end
def edit
end
def add
end
end
Comment controller is as follows:
class CommentController < ApplicationController
def list
end
def destroy
end
def edit
end
def update
end
end
I don't know how to organize the structure.
I would have separate controllers for each of the Users Articles and Comments, but nest them with Comments inside Articles, and Articles inside Users. This seems to fit the relationship between the concepts as you've described them
See
http://www.sentia.com.au/blog/when-to-use-nested-controllers-in-your-rails-apps
and
http://guides.rubyonrails.org/routing.html#nested-resources
for more on nesting controllers.
Currently I am using the Last.fm api to return concert data (returns a hash) in my controller, and in the view cycling through this hash to return the data I want. I want this concert data to become more dynamic and put everything into a model. How do I do this? Should I do this in the controller or somehow in the model?
Here is an example of my code
# app/controllers/events_controller.rb
class EventsController < ApplicationController
def index
#events = #lastfm.geo.get_events("Chicago",0,5)
respond_with #events
end
end
# app/views/events/index.html.erb
<% #events.each do |event| %>
Headliner: <%= event["artists"]["headliner"] %>
<% end %>
In this example I would would want and Event Model with headliner as a parameter, and put all 5 of the events into this model.
I believe it's a good idea to have a model. There are several advantages as I can see
1 - You could access data as OO way as other objects
2 - if you have some business logics (Ex: calculations) you could do it in the model itself with out messing up your view
3 - it's clean and DRY
Example model class would be (this is not a working model, but just to give you an idea):
class Event
attr_accessor :headliner
def self.event_list(limit = 5)
lastfm.geo.get_events("Chicago",0,limit)
end
end
So you can clean-up your view as
<% Event.each do |event| %>
Headliner: event.headliner
<% end %>
It's difficult to thoroughly answer this question without more knowledge about the last.fm API. As a general rule, you want to keep most of your complex logic and relationship data in the model.
For example, you already know you need an Event model, but it also looks like you might need an Artist model as well. You might end up with something like this:
# app/models/artist.rb
class Artist
attr_accessor :name
def initialize(name)
self.name = name
end
end
# app/models/event.rb
class Event
attr_accessor :artists
def initialize(attributes)
#attributes = attributes
self.artists = attributes['artists'].map do |artist_name|
Artist.new(artist_name)
end
end
def headliner
#headliner ||= self.artists.detect do |artist|
#attributes['headliner'] == artist.name
end
end
end
# app/controllers/events_controller.rb
class EventsController < ApplicationController
def index
#events = #lastfm.geo.get_events('Chicago', 0, 5).map do |event_attributes|
Event.new(event_attributes)
end
respond_with #events
end
end
You might also want to look into ActiveModel, which has some helpful features for models that aren't database-backed and can't inherit from ActiveRecord::Base.