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.
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>
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
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.
Newbie question
I'm building an employee form, and one of the field in the form is a choice list where the data comes from department.
ActiveRecords: Employee and Department.
I've to pass both the employee and department active records from the controller to the view. How do I pass these multiple active records to the view to create an employee which uses department to build the choice list?
The common Rails approach is just to set the instance variables in your controller action:
Approach 1:
class EmployeesController < ApplicationController
def new
#departments = Department.all
#employee = Employee.new
end
end
And in your views:
<%= form_for(#employee) do |f| %>
...
<% end %>
Rails does some magic behind the scenes to expose the controller's instance variables to the view template.
A lot of poeple (myself included) feel that allowing a view template object to access the
controller's instance variables directly is a violation of object-oriented programming. An object's instance variables should only be accessible to that instance.
Approach 2:
The better approach is to wrap these variables in getter methods which are then defined as helper methods:
class EmployeesController < ApplicationController
# Make these methods available to the view.
helper_method :departments, :employee
def new
end
# Private - as they're not actions
private
def departments
#departments = Department.all
end
def employee
#employee = Employee.new
end
end
And in your views:
<!-- NOTE - no # sign, it's not an instance variable -->
<%= form_for(employee) do |f| %>
...
<% end %>
In this case, you're defining a public API for the EmployeeController with two methods, departments and employee, which may be accessed.
Practically, this achieves the first thing as the first example; the difference is mostly academic.
Approach 3:
For complex views that need to access a few different variables defined in the controller, you could design your own presenter (or facade) object.
This is probably overkill for your employee form, but to use that as an example:
class EmployeesController < ApplicationController
# Make these methods available to the view.
helper_method :employee_form_presenter
def new
end
# Private - as they're not actions
private
def employee_form_presenter
#employee_form_presenter = EmployeeFormPresenter.new(params) # you can pass args from the controller if you need them
end
end
Create a presenter:
# in app/presenters/employee_form_presenter.rb
class EmployeeFormPresenter
attr_reader :employee, :departments
def initialize(eployee_atts={})
#employee = Employee.new(eployee_atts)
#departments = Department.all
end
end
And in your view:
<%= form_for(employee_form_presenter.employee) do |f| %>
...
<% for dept in employee_form_presenter.departments do %>
...
<% end %>
<% end %>
This keeps your controller really clean and simple - making it easier to test and add extra behaviour to.
All of these approaches work. A presenter is only really required when your controller starts to get more complex. But personally, I
avoid calling a controller's instance variables from the view as a matter of best-practice.
Say your view name is employee_form.html.erb then in your controller there should be a method with same name(not necessary).
def employee_form
#employee = Employee.first
#department = Department.first
end
then you can access these instance variables on your employee_form.html.erb view like
<%= #employee.name %>
<%= #department.name %>
etc.
So I have some data that gets pulled from another rails app in a controller lets call it ExampleController and I want to validate it as being there in my model before allowing the wizard to move to its next step and I can't quite figure out how I should be doing it (I know that getting this data directly from the controller into the model violates MVC I am looking for the best workaround to get my data from the controller) . The data must come from the controller as the methods for getting it are contained in ApplicationController however I could do this in the Awizard controller if this is easier. (Also I cannot use a gem)
Please offer some kind of suggestion to the problem and not an explanation of why this is not the correct way to do things I realise that already but cannot do it another way.
The Example Controller
should this instead render the data then check it isn't blank elsewhere?
class ExampleController < ApplicationController
def valid_data?
data = #data could be nil or not
if data.blank?
return false
else
return true
end
end
My Model - (models/awizard.rb)
How do I use the valid_data? method from the example controller? in my validation here.
class AWizard
include ActiveModel::Validations
include ActiveModel::Conversion
include ActiveModel::Dirty
include ActiveModel::Naming
#This class is used to manage the wizard steps using ActiveModel (not ActiveRecord)
attr_accessor :id
attr_writer :current_step #used to write to current step
define_attribute_methods [:current_step] #used for marking change
validate :first_step_data, :if => lambda { |o| o.current_step == "step1" };
def first_step_data
#What should i put here to check the valid_data? from the examplecontroller
end
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def current_step
#current_step || steps.first
end
def steps
%w[step1 step2 step3] #make list of steps (partials)
end
def next_step
current_step_will_change! #mark changed when moving stepped
self.current_step = steps[steps.index(current_step)+1] unless last_step?
end
def previous_step
current_step_will_change! #mark changed when moving stepped
self.current_step = steps[steps.index(current_step)-1] unless first_step?
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
def all_valid?
steps.all? do |step|
self.current_step = step
valid?
end
end
def step(val)
current_step_will_change!
self.current_step = steps[val]
end
def persisted?
self.id == 1
end
end
Or do I need to add this to this view?
(/views/awizard/_step1.html.erb)
<div class="field">
<%= f.label 'Step1' %><br />
#This is the step I want to validate
</div>
I maybe have misunderstood the question since my answer is simple. However here's a solution that doesn't resort to metaprogramming, but to the fact that Wizard (the class not objects it creates ) is a singleton/constant.
class ExampleController < ApplicationController
def valid_data?
data = #data could be nil or not
result = data.blank?
Awizard.valid_data= result
result
end
end
class Wizard
cattr_accessor :valid_data
def valid_data?
self.class.valid_data
end
end
If course ExampleController#valid_data must have been called before you play around with a Wizard passing step_one.
UPDATE:Reasoning about the global state problem
(raised by #Valery Kvon)
The argument is that Wizard is global to the application and that #wizard instances will be dependant on a global state and are therefore badly encapsulated. But Data, coming from another site, is gloabl in the scope of your app. So there's no mismatch with Wizard beeing the one holding the data. On the contrary it can be considered as a feature.
One example. Wizards magic is only efficient at full moon.
Application SkyReport sends data :
:full_moon => true
It affects all wizards in stage 1 if they need to go on step2 of their power. Therefore relying on the global state of Wizard.valid_data? is exactly what we want...
However if each wizard has a personal message coming from Gandalf's application, then we'll want to inforce the invocation of Gandalf's data but then the solution is even simpler :
# in example_controller.rb
before_filter :set_wizard_data, :only => [:create, :update]
....
def set_wizard_data
#wizard = Wizard.find params[:id]
#wizard.valid_data= valid_data
end
But this again implies that Gandalf.app knows (something of) the #wizard and from how the problem is presented, data coming from the other site is pretty agnostic !
The issue here is that we don't know enough about the app, its requirements and underlying logic to decide what's good or not...
The only way to share controller level data with model is through external accessor.
Using metaprogramming you can trick the way to pass it to a model instance.
controller
def valid_data?
data = #data could be nil or not
result = data.blank? ? false : true
instance_eval <<-EOV
def AWizard.new(*args)
super(*args).tap {|aw| aw.external_valid = #{result}}
end
EOV
result
end
model
class AWizard
attr_accessor :external_valid
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
validate :first_step_data, :if => lambda { |o| o.current_step == "step1" };
def first_step_data
# :external_valid would be true or false according to a valid_data?. Nil would be if valid_data? has not been called
if external_valid == false
errors.add ...
end
end
end
You can pass the data from the controller as a parameter to the validation method in the model.
In your models/awizard.rb
def valid_for_step_one?(some_external_data)
#validation logic here
end
So that in your controller, you can can call:
model.valid_for_step_one?(data_from_controller)
It would also be good if you can give a description of the data you are getting from the controller. How is it related to the model awizard?
Because another option is to set the external data as an attribute of the model. Then you can use it in your validation functions from there.
So I tried to edit and add to the #charlysisto question as this was closest to the answer but it did not work so here is the solution I used, as suggested the answer was to send the data from the controller to the model (Although the answers left out using the view to call the controller method) here is my solution
Model - models/awizard.rb
class Awizard
include ActiveModel::Validations
cattr_accessor :valid_data
validate :data_validation :if => lambda { |o| o.current_step == "step1" }
def data_validation
if self.valid_data == false || self.valid_data.blank?
errors.add(:valid_data, "not found")
end
end
#Other wizard stuff
end
View - awizard/_step1.html.erb
<div class="field">
<% f.label "valid data? %>
<% #_controller.valid_data %> #Call controller method to send data to model
</div>
Controller
class AwizardController < ApplicationController
def valid_data
data = #data from elsewhere
if !data.blank?
Awizard.valid_data = true
else
Awizard.valid_data = false
end
end