Rails 4 pass data from controller to model attr_accessor - ruby-on-rails

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.

Related

Decorator/Presenter/Exhibits and helpers

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>

How to skip a method in a rails model when invoked from different controllers

I am having an active-record model Import
class Import < ActiveRecord::Base
before_create :check_it
def check_it
...
end
end
I am having two controllers User and Question
class QuestionsController < ApplicationController
def import_question
#import_item = Import.new
...
end
end
class UsersController < ApplicationController
def import_user
#import_item = Import.new
...
end
end
I want to skip the check_it method in the Import model for UsersController.
Please help..
The question is, do you really want to do this?
I don't know that the check_it method does, but I think a better way would be to simple call check_it if you need it, for example:
#import_item = Import.new
#import_item.check_it
This makes your code more explicit, and less "magical".
If you really do want to do this, you could do this in your model:
attr_accessor :skip_check_it
before_create :check_it, unless: -> { #skip_check_it }
And then create it like so:
#import_item = Import.new
#import_item.skip_check_it = true

Presenter classes, in a tree model diagram

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

How do I put API data into a model?

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.

rails: how do i access a method in my application controller?

Noob scoping issue, I imagine. :\
class ApplicationController < ActionController::Base
protect_from_forgery
#locations = get_locations
def get_locations
Location.where(:active => true).order('name').all
end
end
Error:
undefined local variable or method `get_locations' for ApplicationController:Class
Two questions:
1) What's with the error? Am I calling the method incorrectly?
2) How do I access this method from a sub-classed controller?
You're calling get_locations within the class scope, but the method is an instance method, not a class method. If for example you used def self.get_locations then you would be providing a class method, one of which you can use within the class scope (after you have defined it, not before like you're doing).
The problem here is the logic, what is this method for? What do you intend to use #locations for? If it's to go inside your application view, then you should put this method into the ApplicationHelper module, and call it from inside the relevant action. If you'd like it in another view on another controller and you'd like to use #locations inside your locations method, perhaps your setup might look something like this:
PagesController
class PagesController < ActionController::Base
def locations
#locations = Location.where(:active => true).order('name').all
end
end
locations.html.erb
<% #locations.each do |location| %>
<%= # do something with 'location' %>
<% end %>
If you'd like to use this inside of your application.html.erb you can simplify it quite some..
ApplicationController
class ApplicationController < ActionController::Base
protect_from_forgery
def locations
Location.where(:active => true).order('name').all
end
end
application.html.erb
<% locations.each do |location| %>
<%= # do something with location %>
<% end %>
The answer boils down to logic, and to really figure out exactly what you're looking for, more details would probably be required.
You're calling it from the class scope, not from an instance scope. more likely what you want is the following:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :setup_locations
private
def setup_locations
#locations = Location.where(:active => true).order('name').all
end
end
To make your original example work, you'd need to make #get_locations defined on self (which points to the class at definition), like so:
class ApplicationController < ActionController::Base
protect_from_forgery
#locations = get_locations
def self.get_locations
Location.where(:active => true).order('name').all
end
end
The problem with that code is that #locations will only be available from the class level as a class instance variable, which is comparable to a static variable in most other languages, and which probably isn't what you want.
I imagine that this line:
#locations = get_locations
... is trying to access the class level method get_locations and not the instance method.
The clue here is that the error message is showing that it can't find it on the class itself (ApplicationController:Class) and not an instance of that class. That means that you're in the class scope, not instance scope.
This would fix it:
def self.get_locations
Location.where(:active => true).order('name').all
end
Even the question is quite old, you can also call your controller action anywhere just by calling:
ApplicationController.new.get_locations

Resources