Rails Presenter block method - ruby-on-rails

EDIT:
I got many responses with different approaches for solving the problem, thanks a lot!
Sadly, none of them worked until now.
To easily understand and reproduce the failure, I created a small Rails repo on GitHub with a Rspec suite.
One of the specs is passing (where the presenter is initialized in the view).
One of the specs is failing (where the presenter is initialized in the controller).
How make them both pass ?
ORIGINAL QUESTION BELOW:
This is my Presenter:
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
end
This is my Controller:
I initialize my Presenter in the controller, passing the view context of the controller with the presented model.
class UserController
def show
#user = User.find(#.....
#presenter = UserPresenter.new(#user, view_context)
end
end
In my Slim template, I call my Presenter to put the content in a link:
=#presenter.linkify do
p "123"
My problem is, I can't pass the block from the view to my linkify method.
In the with comment marked above code, the passed block is the whole view content, instead of the p 123.
When I initialize my Presenter in the view via: #presenter = UserPresenter.new(#user, self), it works as expected.
How I can make the linkify method uses the provided block, without initializing the presenter in the view ?

Because if you are going to use the yield command, you mustn't specify the &block, since now you are effectively receiving a block as a parameter using normal parameter syntax.
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify() # <-- Remove &block
vc.link_to("foo") do
yield
end
end
end
# ...
# Somewhere else, assuming you have access to #presenter which is an instance of
# UserPresenter
# ...
def show
#presenter.linkify do
# ...
# do my view stuff here
# ...
end
end
show()
# Now, if your "View" is nothing but a block that needs to get passed in
# then you'd do this...
def show(&block)
#presenter.linkify do
block.call()
end
end
# This would be used this way:
show(lambda {
# ...
# View stuff here
# ..
})

As specified in lacrosse's answer. The wrong view context is the root of this cause. I tried to make a work around for your situation. And, this is how ended up doing it:
I created a helper method in ApplicationHelper:
module ApplicationHelper
def link(presenter)
presenter.linkify(self) do
yield
end
end
end
changed linkify() to:
def linkify(vc)
vc.link_to("foo") do
yield
end
end
which means, no need to have vc in presenter's class constructer, or you can update the vc in link method defined in the helper(your choice).
views are now looks something like this:
presenter_from_view.html.slim:
-#presenter = UserPresenter.new(#user, self)
=link #presenter do
p 123
presenter_from_controller.html.slim:
=link #presenter do
p 123
I agree, maybe this is not how you wanted your solution to be done. But, I couldn't get any cleaner work around for this. However, here you don't have to worry about passing self in views wherever you use link #presenter do..(which may become too much for writing code when you use linkify in multiple views I guess).
P.S.: Your all specs are passing now. And, if you need the modified code then I can push it to your repository in a separate branch. Let me know.

From Slim's documentation on Helpers and Capturing:
module Helpers
def headline(&block)
if defined?(::Rails)
# In Rails we have to use capture!
"<h1>#{capture(&block)}</h1>"
else
# If we are using Slim without a framework (Plain Tilt),
# this works directly.
"<h1>#{yield}</h1>"
end
end
end
Can you try using capture as follows?
def linkify(&block)
result = capture(&block)
vc.link_to("foo") do
result
end
end

The wrong view context is causing this issue. Just change UserPresenter#initialize to not accept view context, initialize presenter in the controller and pass the correct view context from the view instead, like so:
= #presenter.linkify(self) do
p "123"

What error are you getting? Just looking at the code...
In this method
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
where is vc defined?
I think you mean #vc which is the instance variable you're initializing.
Also as a side note... the empty () in linkify() are redundant in a ruby method with no variables. You can eliminate them.
Also you may want to take a look at the cells gem. As you're basically mirroring this behavior in your presenters and IMO cells is a cleaner way of accomplishing this in rails.

I think I figured out WHY it's not working. When you pass in the view_context in the controller, it's rendering the view_context once when you pass it in to the presenter, and then you again when you actually render the view.
def initialize(user, vc)
#user = user
#vc = vc
end
# When called from the view:
#presenter = UserPresenter.new(#user, self)
# You're passing the view directly in "as is" and renders as expected.
# However, when you pass it in from the controller:
#presenter = UserPresent.new(#user, view_context)
# you're essentially rendering the view_context in the controller, and then again
# once it renders at the end of your action. That's why you're getting this:
# "<p>123</p><p>123</p>"
You'll either need to send self in from the view each time you call linkify, or you can use an application helper method that will always have the view context.

Related

Set a reference field for all saved objects

My application has a Group object, of which a user has many. The navbar displays which group is currently selected, and the objects displayed on various pages are based on this. A number of my models have 'group_id' fields, and I'd like for these fields to be populated with the id of the currently selected group when they're saved.
In my application controller I have a helper_method which returns the current_group however this can't and shouldn't be accessed from a model which is the DRYest way I could think of doing this.
#inhereted_model.rb
before_save :assign_group_reference
def assign_group_reference
self.group_id = current_group.id
end
Is there an efficient and DRY way to do this that I'm missing?
You are right; any controller helper-methods cannot and should not be accessed directly from a model method.
I think a standard DRY way is to set the parameter of a model in your Controller methods. For example, do as follows in your Controller(s):
# In a Controller
def my_helper(mymodel)
mymodel.group_id = current_group
# where current_group is your Controller helper method to obtain the group name.
end
def create # or update etc.
#mymodel = set_my_model # your arbitrary method to set a model
my_helper(#mymodel)
respond_to do |format|
if #mymodel.save
format.html { redirect_to #mymodel, notice: 'Success.' }
else
raise
end
end
end
If you want, you can write my_helper (which in this case takes no argument and sets the instance variable #mymodel instead of a local variable) in before_action in combination with only or except, where you make sure the method is called after a model #mymodel is set, in order to avoid calling my_helper repeatedly in many methods in the Controller.
Alternatively, if you really want to set it at a model level for some reason, a potential workaround is to use a Ruby Thread variable, like the following.
# In a controller
def create
model = set_my_model # Arbitrary routine to set a model
Thread.new{
Thread.current.thread_variable_set(:grp, current_group)
# where current_group is your Controller helper method to obtain the group name.
model.save!
# In the model, you define a before_save callback
# in which you write something like
# self.group_id = Thread.current.thread_variable_get(:grp)
}.join
end
But I think this is a little dirty hack and I would avoid it in principle.

Rails 5 API: custom hidden responder that would process value returned by the action

I have rails 5 based api app, using fast_jsonapi
and after a while I observe that all most all my actions are having one common pattern
def action_name
#some_object.perform_action_name # this returns #some_object
render json: ControllerNameSerializer.new(#some_object).to_h
end
I do not wish to write the last render line here and it should work, for that I want that the returned value by the action should be processed by any hidden responder like thing, Serializer klass can be made out looking at controller name.
Perhaps this could be achieved by adding a small middleware. However at first, I find it not a good idea/practise to go for a middleware. In middleware, we do get rendered response, we need a hook prior to that.
I would imagine like
class SomeController ...
respond_with_returned_value
def action_name
#some_object.perform_action_name # this returns #some_object
end
Any suggestions?
Note, do not worry about error/failure cases, #some_object.errors could hold them and I have a mechanism to handle that separately.
Sketched out...
class ApplicationController < ...
def respond_with_returned_value
include MyWrapperModule
end
...
end
module MyWrapperModule
def self.included(base)
base.public_instance_methods.each do |method_name|
original_method_name = "original_#{method_name}".to_sym
rename method_name -> original_method_name
define_method(method_name) { render json: send(original_method_name) }
end
end
end
Seems like there really should be some blessed way to do this - or like someone must have already done it.

How to get the current controller instance

I have a controller, that calls another class (say, a mutation) to perform some action. I want to be able to instruct this mutation to interact control flow, e. g. to redirect_to or to show flash notice. In pseudocode this looks like:
my_controller.rb
def create
MyCreateMutation.run!(params).tap do |result|
render result ? :success : :error
end
end
my_mutation.rb
def execute **params
begin
# do creation
rescue => e
# ⇓⇓⇓⇓⇓
flash[:error] = e.message
end
end
The problem with the code above is that flash is local to current controller. I know, that I can:
pass current controller instance to a mutation and call flash on it;
declare static current method on the very top application controller and use this “global” variable;
parse a caller array and constantize the topmost found controller;
introduce a middleware and save the current controller instance somewhere.
All the above looks like an overkill to me.
Is there a common way to get the current controller, if it is presented on stack (somewhere on the stack there is a method of ApplicationController)?
Well, for future visitors: I finally stuck to declaring current class variable on the very top ApplicationController:
cattr_accessor :current
before_filter { ApplicationController.current = self }
after_filter { ApplicationController.current = nil }
After that, I have a current controller instance available via:
ApplicationController.current

Ruby on Rails - Creating and Using a Custom Method

I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.

Accessing loop variable in other methods

Let's say I have the following method:
def run
#users.each do |u|
..
..
end
end
I have a lot of code in run so I am trying to refactor it and splitting it up into smaller methods. One of these methods is the following:
def finish_mutation
..
..
Rails.logger.info "Succesfully added #{u.name}"
end
This breaks because finish_mutation doesn't have access to the u variable. How can I create new methods that can access the u variable that I created in run?
You can simply create method taking parameter:
def finish_mutation(user)
# code
Rails.logger.info "Successfully added #{user.name}"
end
and call it, passing User instance:
finish_mutation(u)
it's easy to do you just add a parameter to your finish_mutation method like this :
def finish_mutation(param)
# .......
end
then you call your function like this :
def run
#users.each do |u|
..
..
finish_mutation(u) # <----- for example here you call your method
end
end
Sometimes passing your loop variable (as shown in the other answers) is the best answer. Sometimes you can DRY things up better by adding a method to whatever class 'u' is an instance of. So you might do
class User
def finish_mutation
# put your operation here
end
end
And then in your loop
u.finish_mutation
Obviously you need to think about which is the best way for a specific case.

Resources