How to clean up messy views and controllers in Rails? - ruby-on-rails

I've got a lot of code like this in my app:
if #document.template.name == "Newsletter"
...
end
Which I realise is poor and ugly code. I'm not sure though what alternatives exist for this kind of code. Are there any best practices for it? I hope so. Cheers!
Sample controller code
In this controller code sample it posts an image to Twitter if the name is "Newsletter". I know it's messy, and that a lot of the code should be moved to the model. I'm more concerned about the conditional though.
if #document.template.name == "Newsletter"
source = Magick::Image.read(#document.component.image_newsletter.path).first
overlay = Magick::Image.read(#document.user.logo.path).first.resize_to_fit(source.columns)
rec = Magick::Draw.new
rec.stroke = "##{#document.user.colour1}"
rec.fill = "##{#document.user.colour1}"
rec.rectangle 0, 0, source.rows, 5
lank = source.extent(source.columns, source.rows+overlay.rows, 0 ,0)
combo = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(combo)
client.update_with_media("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}...", open(combo.to_blob))
else
client.update("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}... http://domain.com#{share_path(#document.user.ftp, #document)}")
end

Presenter Pattern to the rescue
app/helpers/application_helper.rb
This will give you convenient access to instantiate a presenter anywhere in any of your views.
Example, if you use present #document it will instantiate a DocumentPresenter.
module ApplicationHelper
def present object, klass = nil
klass ||= "#{object.class}Presenter".constantize
presenter = klass.new object, self
yield presenter if block_given?
presenter
end
end
To override the presenter used, you can do present #document, MyPresenter
app/presenters/document.rb
Your actual presenter. Create as many instance methods as you like and keep all of the view logic in here. You have access to all view helper methods through #template
class DocumentPresenter
def initialize document, template
#document = document
#template = template
end
def name
if #document.template.name == "Newsletter"
# for example ...
#template.link_to 'Newsletter', #template.document_index_path
end
end
def description
#template.content_tag :p, #document.description, class: "description"
end
end
app/views/document/show.html.erb
<% present #document do |document_presenter| %>
<div id="document">
<%= document_presenter.description %>
<%= document_presenter.name %>
</div>
<% end %>
Result
<div id="document">
<p class="description">
lorem ipsum
</p>
Newsletters
</div>
You can learn more about the Presenter Pattern as done by Ryan Bates in his RailsCast episode "Presenters from Scratch"

The only alternative I can think of presently is to move the template-specific code in to the Template model, separated in to individual methods which follow a particular naming convention.
For example, your methods could follow the convention process_x, where x is the name of the template. In this case, the code you posted for the "newsletter" would be in a method called process_newsletter.
I would also create a single point of entry, lets call it process, in the same model, which is responsible for delegating to one of these methods, like so:
class Template < ActiveRecord::Base
... other model code
def process # this is the method to be called from the controller
method_name = "process_#{self.name}" # the name of the method to be called
send method_name # call a method by this name
end
def process_newsletter
# your newsletter code already posted
end
def process_article # another example for illustration purposes
# article specific code
end
end
This not only eliminates the need for template name checking, but also helps to further separate your code, and moves any model-specific stuff away from the controller.

Related

How can I access a method in a controller that is defined in a model?

I'm pretty new to rails, and the project I'm working on requires me to access an existing method. However, it's written in a model, and I'm not sure how I can get it to run in the controller my API needs to hit
I've tried routing to the method in the model, but learned I can't do that. From what I've gathered, this is sort of the way it will work, right?
model.rb
def method_i_need
//code
end
controller.rb
def method_to_call_other_method
//code
end
At the risk of stealing #sergio's points...
If your method is defined inside Model.rb, then both the following will work in your controller:
def method_to_call_other_method
Model.first.method_i_need
end
def method_to_call_other_method
Model.find(params[:id]).method_i_need
end
As the commentor said, you just need an instance of your model (Model.first or Model.find(params[:id])) and to then call the method you defined in your model, on the instance of the model. And the params[:id] is obviously dependent on what params you're getting through.
Any instance of a class will have the public instance methods available to be called on the instance object. It's very common to instantiate model class instances within a controller action.
Here's an example elaborating on previous answer and comments how you can do this in Rails.
class Person < ActiveRecord::Base
def say_hello
language == 'DE' ? 'Guten Tag' : 'Hello'
end
end
class PersonsController < ApplicationController
def random_person
#random_person = Person.find(Person.pluck(:id).sample)
# you can now call #random_person.say_hello
end
def person_greetings
# this examples assumes we only have 2 languages, EN and DE
languages = Person.pluck(:language).uniq.sort
#greetings = languages.each_with_object({}) do |language, hash|
hash[language] = Person.new(language: language).say_hello
end
end
end
# #greetings should return the following hash
=> {
"DE" => "Guten Tag",
"EN" => "Hello"
}
Likewise, class methods can also be called directly when needed inside a controller action method for example in model you may have a class method defined like this inside the Person model.
def self.languages
pluck(:language).uniq.sort
end
This method can be called from any controller or other classes where appropriate for example:
def languages
#people_count = Person.count # active record method to get number of people in database
#languages = Person.languages
end
Where you might use this inside of a controller action's view
<div>
There are <%= #people_count %> total people in the system.
Of them, <%= #languages.count %> languages are spoken.
Those include the following:
<ol>
<% #languages.each do |language| %>
<li><%= language %></li>
</ol>
</div>

Using other methods in `show`/`index` page

My show action in a chair controller creates an acronym from a Chair's name. The following codes works:
def show
#chair = Chair.find(params[:id])
#user = #chair.user
first_letters = []
#chair.name.split.each do |word|
first_letters << word[0]
end
#names = first_letters.join
end
I want to extract an acronym method that takes care of creating the acronym. I tried this:
def show
#chair = Chair.find(params[:id])
#user = #chair.user
#names = #chair.acronym
end
def acronym
first_letters = []
#chair.name.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
However, acronym couldn't be accessed from show. The method names and the views need to match, but is there a way to have a separate method acronym like this?
In order for #chair.acronym to work you need to define the acronym method on the Chair model.
Alternatively, you can also refactor like this, keeping the acronym method in your controller:
def acronym(chair)
first_letters = []
chair.name.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
You would call this method like this:
#names = acronym(#chair)
There are a few other options you have (decorators/presenters) outside the scope of this question (but may be worth investigating if you're interested in design/application structure)
If you move it into the Chair model in chair.rb, you can write it as:
def acronym
first_letters = []
name.split.each do |word|
first_letters << word[0]
end
first_letters.join
# Alternative, one line
# name.split.map(&:chars).map(&:first).join('')
end
Then in your controller you can call #chair.acronym
The Rails way is supposed to put the business logic to in the Model file.
We should put the acronym method logic in our model file and define it as instance method.
Then call acronym method from the controller with #chair object as:
#chair.acronym
Hope it helps...
If you want to setup acronym as a new action on your controller you'll need to update your config/routes.rb. The simplest way would be to just add a static route:
get 'my_controller_name/acronym'
You'll also need a corresponding view to render your result in app/views/my_controller/acronym.erb
If you just want to play around without having to implement a view, you can just render text from your action without using a template (view). Just add this to the end of your acronym action:
render plain: first_letters.join
Everything you want to know is in the rails guide on routing.

Calling method from module in view

How do I call a method from a module in a view?
Made in /lib folder "util.rb"
module Util
def something
....
end
end
in controller
require 'util'
in view
<% name = ??? Util.something ??? %>
It'll be great to see the actual use case as there are a couple of ways to do it. I'd also recommend using a helper for views as that's what they are designed for.
But in your case, the only thing to make it work, is to set a self on that method.
module Util
def self.something
puts 'hi'
end
end
then you can easily call it in your view:
<% hello = Util.something %>

How to DRY up 2 controllers/models/views that are basically the exact same

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.

Ruby metaprogramming inside model

I have a model called Snippet, which contains snippets of HTML to push into views.
The model has a column CODE and another CONTENT
I'd like to write something like this in my view and get the content back
<%= raw Snippet.PHONE_NUMBER %>
which looks up PHONE_NUMBER on CODE and returns CONTENT
Add a method_missing class method in Snippet class as follows
# Snippet class
class << self
def method_missing(method, *args, &block)
if(snippet = Snippet.find_by_code(method.to_s))
return snippet.content
else
return super(method, *args, &block)
end
end
end
This should do the trick.
However, on a related note, I'm not sure if doing this would be the best way to go because your code is dependent on the data in your database. Tomorrow, the record for phone number gets removed and your code Snippet.PHONE_NUMBER would break. There is a lot maintenance headache in this approach.
A cleaner approach (which would avoid metaprogramming) would have your view do something like this:
<%= snippet :PHONE_NUMBER %>
or
<%= snippet 'PHONE_NUMBER' %>
where the snippet method is defined in a helper module like this:
module SnippetHelper
def snippet(code)
raw Snippet.find_by_code(code.to_s).content
end
end
and made available to all your views with something like this:
class ApplicationController < ApplicationController::Base
helper :snippet
end
Or use delegate.
But it sounds like you're providing another implementation of partials, or helpers, or a combination of decent_exposure and some combination of helpers and partials.

Resources