I have a class Thingy, whose objects often need to create links to other websites. So far the only approach working for me is the following:
class Thingy < Active:Record::Base
def makeLink
result = ""+self.secondProperty+""
end
...
Now in any view I could use this link-method as follows
thingy.makeLink.html_safe
Somehow I feel there should be a much better way. What is the right approach here?
the proper approach would be to add a ThingyHelper in app/helpers
def behavior_describing_method_name(thingy)
link_to thingy.secondProperty, "someUrl#{thingy.firstProperty}"
end
Related
An Order has_many AItems and BItems. As you can tell, the items are basically identical but with an important business reason for categorizing them separately. Wondering what's the best strategy to DRY this up. I realize this is a little opinionated... but hoping to get some clear points of view and arguments.
View code
Currently I'm using a partial. Like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> AItems
> new.html.erb
> BItems
> new.html.erb
# routing
get '/AItems/new'
get '/BItems/new'
# code for /views/AItems/new.html.erb
<%= render "layouts/items_new", object: "AItems" %>
# code for /views/BItems/new.html.erb
<%= render "layouts/items_new", object: "BItems" %>
I'm wondering if it'd be easier to get rid of the partial entirely and just do parameters like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> Items
> new.html.erb
# routing
get '/items/new/:type'
# code for /views/Items/new.html.erb
# code from partial evaluating the param[:type] instead of a passed object
Controller code
Currently everything is duplicated... (I haven't made any attempt at DRYing yet) as in it looks like this (very illustrative, the point is to just show that short of the naming conventions literally everything is basically the same):
class AItemsController
def new
#items = AItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
a_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
class BItemsController
def new
#items = BItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
b_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
I haven't DRYed this yet because I'm a little conflicted as to how. Examples below are illustrative, forgive if the code isn't exact, but hopefully you get the gist.
Solution A: In one way, I could keep the action definitions in each controller, and then have the code within the action pull from a shared concern:
class AItemsController
include SharedCode
def new
shared_new
end
def do_something
shared_do_something
end
end
Solution B: abstract away the action definitions to the shared concern:
class AItemsController
included SharedAction
shared_action("AItems")
end
Solution C: route everything to a singular controller and again use params to differentiate (passed from view)
class ItemsController
def new
item_type = params[:item_type]
end
def do_something
item_type = params[:item_type]
end
end
Model code
This one is a little more cut and dry, and I don't need a ton of feedback here, I will just used shared concerns for key methods/ callback.
Obviously the answer for one will affect the other. For example if everything routes through a single controller, then I'll have a single view with parameters rather than a partial approach. But because the controller has multiple DRYing options, there's still room for debate.
If you've read this far, I will happily take angry comments about how this question is too loosely defined in exchange for at least some thoughts on what you would do. What's more understandable for you if you were taking over my code?
I am trying to learn and the best way to do that is to solicit multiple points of view and pros and cons to weigh out.
Check out the InheritedResources Gem: https://github.com/josevalim/inherited_resources
Inherited Resources speeds up development by making your controllers
inherit all restful actions so you just have to focus on what is
important. It makes your controllers more powerful and cleaner at the
same time.
Or the Responders Gem, a replacement to Inherited Resources: https://github.com/plataformatec/responders
A set of responders modules to dry up your Rails 4.2+ app.
I've been researching the 'recommended' way to use Rails view helpers (e.g. link_to, content_tag) in a plain ruby class, such as a presenter. It seems there's very little information on this front and I wanted to get an idea of what the Stack community thought.
So, the options we have are.. (note I'm using Rails 4, and am less concerned about older versions)
Include the required modules manually
This is probably the cleanest way, since only the helpers needed are included. However I have found this method to not work in some cases, as the usual view context provided in plain Rails helpers is configured for the current request. url_for wouldn't know about the current request for example, so the host might not match.
class MyPresenter
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::CaptureHelper
def wrapped_link
content_tag :div, link_to('My link', root_url)
end
end
Use ActionController::Base.helpers
Since Rails 3, ActionController::Base has included a helpers method to access the current view context. I believe the view context provided by this method is configured as it would be in a rails helper, but I might be wrong. There's not really any documentation about this which seems worrying, but it does work quite well in practice.
class MyPresenter
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
protected
def h
ActionController::Base.helpers
end
end
I believe this view context can also be mixed in with include, but the rails view helpers have hundreds of methods and it feels dirty to include them all indiscriminately.
Inject the view context when calling the presenter
Finally, we could just pass the view context to the class when it's initialized (or alternatively in a render method)
class MyPresenter
attr_accessor :context
alias_method :h, :context
def initialize(context)
#context = context
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
end
class MyController < ApplicationController
def show
# WARNING - `view_context` in a controller creates an object
#presenter = MyPresenter.new(view_context)
end
end
Personally I tend to lean towards the latter two options, but with no definitive answer from the Rails team (that I've been able to find) I felt a bit unsure. Who better to ask than Stack!
I would go with the mix of the second and third option, something like:
class MyPresenter
def initialize(helpers)
#h = helpers
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
private
attr_reader :h
end
Your second option require all your unit tests to be stubbed as ActionController::Base.helpers which maybe isn't a good option and your third option you're using a huge context to access just some methods.
I would really make that dependent on what kind of methods you use. If it's just the basics like content_tag etc. I would go for the ActionController::Base.helpers way. It is also possible to call some helpers directly, e.g. for paths inside models I almost always use something along the lines of Rails.application.routes.url_helpers.comment_path.
For controller-specific stuff the third option might be useful, but personally the "pure" way seems nicer. Draper has an interesting approach too: They save the view_context for the current request and then delegate the calls to h-helpers to it: https://github.com/drapergem/draper/blob/master/lib/draper/view_context.rb
It really is just a matter of preference. I would never include all helpers at once, as you already said. But the second option is quite nice if you want to build the presentation layer yourself without using a gem like Draper or Cells.
I currently have an already-large controller that’s getting bigger. I was wondering what would be the best way to slim down my controllers. I’m not necessarily looking for the easiest way, but a safe and efficient way. I’ve been developing with Rails for a while now but I’m still not familiar with how “subclassing” works and I’m not even sure if it’s supposed to be used in this way. I was thinking maybe something like this?
class SomeController < ApplicationController
end
class MoreFunctionsController < SomeController
end
That’s currently untested – I’m still working on it right now – but I hope that this would sort of give you an idea of what direction I’m trying to go. I’m also not sure how the routing for this would look. What would be the best way to “split” up a large controller?
ActiveSupport::Concern (documentation) is what you are looking for.
Update
Something like this:
# config/application.rb
config.autoload_paths += %W(#{Rails.root}/app/controllers/concerns) # in Rails4 this is automatic
# app/controllers/my_controller.rb
class MyController < ApplicationController
include GeneralStuffConcern
def index
render text: foo
end
end
# app/controllers/concerns/general_stuff_concern.rb
module GeneralStuffConcern
extend ActiveSupport::Concern
def show
redirect_to root_path
end
protected
def foo
'fooo'
end
end
update 2
I actually recommend this more http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
update 3 (2022)
Bounded contexts https://blog.eq8.eu/article/rails-bounded-contexts.html
Some recommendation [1] suggest you to use
<%= current_user.welcome_message %>
instead of
<% if current_user.admin? %>
<%= current_user.admin_welcome_message %>
<% else %>
<%= current_user.user_welcome_message %>
<% end %>
But the problem is you must have the decision logic somewhere in your code.
My understanding is putting the decision in template is better than controller as it make your controller more clean. Is it correct?
Are there better way to handle this?
http://robots.thoughtbot.com/post/27572137956/tell-dont-ask
You are not the first to wonder this. If views and controllers should have little to no logic, and the model should be presentation agnostic, where does presentation logic belong?
Turns out we can use an old technique called the decorator pattern. The idea is to wrap your model object with another class that contains your presentation logic. This wrapper class is called the decorator. The decorator abstracts away logic from your view, while keeping your models isolated from their presentation.
Draper is an excellent gem that helps define decorators.
The sample code you gave could be abstracted like so:
Pass a decorator to the view with #user = UserDecorator.new current_user in your controller.
Your decorator could look as below.
class UserDecorator
decorates :user
def welcome_message
if user.admin?
"Welcome back, boss"
else
"Welcome, #{user.first_name}"
end
end
end
And your view would simply contain #user.welcome_message
Notice that the model itself doesn't contain the logic to create the messages. Instead, the decorator wraps the model and translates model data into a presentable form.
Hope this helps!
I would use a helper for this. Suppose you have to translate the welcome-message, based on some locale.
In the app/helper/user_helper.rb write
module UserHelper
def welcome_message(user)
if user.admin?
I18n.t("admin_welcome_message", :name => user.name)
else
I18n.t("user_welcome_message", :name => user.name)
end
end
end
and in your view you can then just write
<%= welcome_message(user) %>
Note that the decorator/presenter offers a really clean object-oriented approach, but imho using a helper is much simpler and sufficient.
No, you don't want any conditionals at all in the user class nor the controller. The point of that example on the blog post is to make reference to polymorphism, just good old fashioned OO design.
# in application_controller for example
def current_user
if signed_in?
User.find(session[:user_id])
else
Guest.new
end
end
#app/models/user.rb
class User
def welcome_message
"hello #{name}"
end
end
#app/models/guest.rb
class Guest
def welcome_message
"welcome newcomer"
end
end
...you get the idea.
Only, instead of littering your model with presentation-only methods, create a decorator that acts as a presenter:
require 'delegate'
class UserPresenter < SimpleDelegator
def welcome_message
"hello #{name}"
end
end
And now current_user looks like so:
# application_controller
def current_user
if signed_in?
UserPresenter.new(User.find(session[:user_id]))
else
Guest.new
end
end
Decorate the user model and add the welcome_message to it directly. Yes, this may involve some kind of conditional statement at some point.
http://robots.thoughtbot.com/post/14825364877/evaluating-alternative-decorator-implementations-in
In my opinion, if the text is the only thing that changes, it doesn't belong in a view. If you needed to restructure the page, that's presentation logic. This, this is just data being different.
I think you should watch the railscasts episode on Presenters for the answer.
Logic in the view is hard to maintain, we should put the business logic in the model and all view logic in helpers.
If you want your code to be in Object Oriented fashion, make use of Decorators (object oriented way of helpers)
Best Example : https://github.com/jcasimir/draper
Put the code defining current_user.welcome_message in _app/helpers/application_helper.rb_, it will then be accessible by any view rendered with the application layout.
Another option is to define a custom helper module, one which is not necessarily associated with a given view or controller (See the video I linked below), and include it in the modules of the view/controllers you wish to have that functionality in.
This is not something that is black and white. But, from what you have described it sounds like this is code that is obtrusive to stick in your application_controller.rb and it is not code with functionality which justifies it's own controller, the most effective and efficient option may be to create a custom helper module and include it in the helpers you wish to have that functionality. That said, this is ultimately a judgement call which the designer of the application (i.e. you) needs to decide upon.
Here is a good article outlining helper modules from May, 2011
Here is is a RailsCast outlining custom helper modules (i.e. custom as in modules not necessarily associated with a given controller or view). Short, sweet, and to the point.
You can define helper method for that stuff. I don't think it is a good Idea to make a welcome sentences in a model, but in the controller too. But you should try to make you views clean from code, and if you can use helpers for that then you should to.
A good practice would be to have real View instances. Rails parody of MVP (there is difference, look it up) unfortunately seems to pretend that views are templates. That is wrong.
Views are supposed to contain the presentation logic in MVC and MVC-inspired patterns. They are also supposed to manipulate multiple templates and make decision on which templates to employ to represent the state and information from the model layer (yes, model is a layer not an ORM instance).
So, to answer the question: presentation logic has no place in controllers.
This is going to sound strange, but hear me out...I need to be able to make the equivalent of a POST request to one of my other controllers. The SimpleController is basically a simplified version of a more verbose controller. How can I do this appropriately?
class VerboseController < ApplicationController
def create
# lots of required params
end
end
class SimpleController < ApplicationController
def create
# prepare the params required for VerboseController.create
# now call the VerboseController.create with the new params
end
end
Maybe I am over-thinking this, but I don't know how to do this.
Inter-controller communication in a Rails app (or any web app following the same model-adapter-view pattern for that matter) is something you should actively avoid. When you are tempted to do so consider it a sign that you are fighting the patterns and framework your app is built on and that you are relying on logic has been implemented at the wrong layer of your application.
As #ismaelga suggested in a comment; both controllers should invoke some common component to handle this shared behavior and keep your controllers "skinny". In Rails that's often a method on a model object, especially for the sort of creation behavior you seem to be worried about in this case.
You shouldn't be doing this. Are you creating a model? Then having two class methods on the model would be much better. It also separates the code much better. Then you can use the methods not only in controllers but also background jobs (etc.) in the future.
For example if you're creating a Person:
class VerboseController < ApplicationController
def create
Person.verbose_create(params)
end
end
class SimpleController < ApplicationController
def create
Person.simple_create(params)
end
end
Then in the Person-model you could go like this:
class Person
def self.verbose_create(options)
# ... do the creating stuff here
end
def self.simple_create(options)
# Prepare the options as you were trying to do in the controller...
prepared_options = options.merge(some: "option")
# ... and pass them to the verbose_create method
verbose_create(prepared_options)
end
end
I hope this can help a little. :-)