How to call method inside Rails Method - ruby-on-rails

I am struggling to call a method in my User Model. I have a long method similar to this:
def find_content
def find_x
# call api
end
def find_y
#call api
end
content = {"x": find_x, "y": find_y}
return content
end
I then try to call it like this in my model:
class User < ApplicationRecord
def User.news
# get result of find_content
content = find_content
# I also tried doing User.find_content when the function was inside the model
## the function then passes the content variable to my UserMailer which sends emails to my users with the content
end
I have tried placing my find_content in the User Model with def self.find_content and without the self part.
I was wondering where is the best place to put a function that can be used like this in the model.

If I where are you I would create a Service class or a lib class, and I would call it.
Don't define your methods inside methods. Try something like this
class MyFancyService
def find_content
{"x": find_x, "y": find_y}
end
private
def find_x
#code
end
def find_y
#code
end
end
And inside your model
#remember to require your libs/services class in somewhere (maybe application.rb)
class User < ApplicationRecord
def news
MyFancyService.new.find_content
end
end
Do not abuse of Class method (def self.bla) you should have more instance methods.

The reason you are having this issue is that finding content is not really a User concern and should be broken out into a separate class as Horacio mentions, but I don't think the User class needs to know anything about finding content. It is possible that you need some User info to properly find content however.
I would suggest something like this (assuming you need something from a User object to call your api)
class User
def user_stuff_needed_by_api
end
end
class NewsAPI
def initialize(user_stuff)
# set stuff needed based on the user
end
def find_x
# call api
"x"
end
def find_y
# call api
"y"
end
def find_content
{"x": find_x, "y": find_y}
end
end
Then in your controller you have the user object, so get what you need from it, create an instance of the api and make your call
user_stuff = #user.user_stuff_needed_by_api
news_api = NewsAPI.new(user_stuff)
content = news_api.find_content
If you really want to make calls to the api inside your a User instance which I don't think you should, I would recommend passing in an instance of the api via a setter and then delegate find_content to that instance. So something like this.
class User
def set_news_api(api)
#news_api = api
end
def find_content
#news_api.find_content
end
end
Lastly if you really want to put all this in the User class something like this should work, but again is not recommended.
class User
def self.find_x
"xx"
# call api
end
def self.find_y
"yy"
#call api
end
def find_content
{"x": self.class.find_x, "y": self.class.find_y}
end
def self.other_find_content
{"other_x": find_x, "other_y": find_y}
end
def user_stuff_needed_by_api
end
end
puts User.new.find_content
puts User.other_find_content

Related

How to access current user in a mailer file before action

I have a before action in a user mailer file, which is supposed to stop mailers sending if a column on user is set to true or false. However current user is currently unavailable. I understand why, but was wondering if there was a way to do this.
I want to avoid adding the check_if_users_can_receive_mailers at the top of each mailer method.
before_action :check_if_users_can_receive_mailers
#methods that send mailers
private
def check_if_users_can_receive_mailers
current_user.send_mailers?
end
You have to make the current user available as a attribute or class variable. The most straight forward method is something like this:
class MailerBase < ActionMailer::Base
before_action :check_if_users_can_receive_mailers
attr_accessor :user
def initialize(user)
#user = user
end
private
def check_if_users_can_receive_mailers
user.send_mailers?
end
end
class SomeMailerClass < MailerBase
end
In Rails only your controller and views are request aware. Mailers and models and other classes in your application are not and they cannot get the current user since they can't access the session nor the method current_user which is a helper method mixed into your controller (and the view context).
If your mailers need to know about the current user the most logical approach is to pass that information into the mailer:
class UserMailer < ApplicationMailer
def intialize(user)
#user = user
end
end
However a mailer should only have one job - to send emails and it shouldn't be questioning if it should do the job or not. Determining if you should send an email to the user should be done elsewhere. You can place this logic in the controller or even better in a service object:
# app/notifiers/user_notifier.rb
class UserNotifier
def initialize(user, event:)
#user = user
#event = event
end
def notify
if #user.wants_email?
spam_user!
end
send_in_app_notification
end
def self.notify(user, event:)
new(user, event:)
end
private
def spam_user!
# ...
end
def send_in_app_notification
# ...
end
end
class ThingsController
def create
#thing = Thing.new
if #thing.save
UserNotifier.notify(current_user, event: :thing_created)
redirect_to #thing
else
render :new
end
end
end

Encapsulating controller logic in rails

I have 2 controllers in rails with different authentications schemes,
but they do almost the same.
What is the best way in rails to encapsulate
the logic of a controller in another class or helper?
Sample:
def ControllerA < BasicAuthController
def create
blablacode
end
end
def ControllerB < TokenAuthController
def create
blablacode
end
end
Whats the proper way to do this? create a model with the code?
Create a helper? other?
The simplest thing is to make a module and then include it into the other controllers:
module ControllerMixin
def create
blablacode
end
end
The remaining question, though, is where to put this code such that it is works with Rails autoloader, since it needs to be loaded before the controllers. One way to do it would be to write the module to a file in the lib/ directory, then add that to the autoload paths (see auto-loading-lib-files-in-rails-4
Why don't you enable both schemes for a single controller? Especially if the only difference is Authentication. You could have two app/controllers/concerns to encapsulate both authentication methods and include Auth1 and include Auth2 for a single controller who is only responsible for whatever resource it manages.
Otherwise, services are the best approach to encapsulate controller logic.
Create a folder called services in your app folder and write PORO classes here. Say you have a few places in your app where you want to pay for stuff via make Stripe.
# app/services/stripe_service.rb
module StripeService
def customer(args)
...
end
def pay(amount, customer)
...
end
def reverse(stripe_txn_id)
...
end
end
# controller
StripeService.customer(data)
=> <#Stripe::Customer>
Or if you only need to do one thing.
# app/services/some_thing.rb
module SomeThing
def call
# do stuff
end
end
# controller
SomeThing.call
=> # w/e
If you need an object with multiple reponsibilities you could create a class instead.
class ReportingService
def initialize(args)
...
end
def query
...
end
def data
...
end
def to_json
...
end
end
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
I do it something like this:
#app/services/my_app/services/authentication.rb
class MyApp::Services::Authentication
class < self
def call(params={})
new(params).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(params)
#params = params
end
def call
... do a lot of clever stuff
... end by returning true or false
end
private
def params() #params end
end
Then:
class FooController < ApplicationController
before_action :authenticate
def authenticate
redirect_to 'some_path' unless MyApp::Services::Authenticate.call(with: 'some_params')
end
end
Short answer, i choose to create a Helper.
From all the suggestions in the answers
Create a Module:
Seems correct but it didnt feel right to have logic outside
the app directory. This wasnt an external module or library but
something very related to the logic of my app.
Integrate diferents authentications in one controller:
Was a good suggestion but i have to change all the logic of my app.
Create a Helpers:
It seems to me the better solution, i had the code on a helper, and
is inside the app directory, very near from the other logic.

Access to class instance variable

How can I read a class variable? In my case I can't get value from variable.
class MediaContentsController < ApplicationController
#randomUsrId = rand(100)
def create
puts #randomUsrId
end
end
First of all, #randomUsrId refers to an instance variable, not a class variable. You can access it through an instance of the class, not direct on the class. For a class variable, you should use ##randomUsrId.
What you are actually looking for is attr_accessor :randomUsrId, through this, you can read it on an instance method, and even can set it through an instance of the class.
Here's how:
class MediaContentsController < ApplicationController
attr_accessor :randomUsrId
#randomUsrId = rand(100)
def create
puts #randomUsrId
end
end
But #randomUsrId = rand(100) won't set #randomUsrId to a random number, at least it is not the recommend way. You should use before_action here.
class MediaContentsController < ApplicationController
attr_accessor :randomUsrId
before_action :set_user_id_to_a_random_number, only: :create
def create
puts #randomUsrId
end
private
def set_user_id_to_a_random_number
#randomUsrId = rand(100)
end
end
Edit:
Each time you call the set_user_id_to_a_random_number function, it will generate a different number based on rand(100) and store it inside #randomUsrId. If that's what you do not want, and you want to persist the same value, you can do something like following:
def set_user_id_to_a_random_number
#randomUsrId = rand(100) unless defined? #randomUsrId
end
Edit:
What I have stated works only for one request, if you have multiple request, it won't work. As Ryan Bates says here:
An instance variable only sticks around for a single request, so using the technique described will only benefit you if you need to call a method multiple times per request.
That leaves you with two options if you want to store something between multiple requests. Either you can go with Databases, or you can use something called memcached.
You probably doing something wrong in your code, since this isn't how Rails controller logic should be usually implemented, but let's get down to your question anyway. As I mentioned it isn't class variable, it's instance variable in class scope, so in order to access it, you should first get it from that scope:
class MediaContentsController
#randomUsrId = rand(100)
def create
puts self.class.get_random_usr_id
end
def self.get_random_usr_id
#randomUsrId
end
end
MediaContentsController.new.create
# => 44

Rails, place to put user input parsing

In my Rails app there is a view with a simple user form consisting of a text box and a submit button.
When the user submits the form, depending on his input, different models are created:
class MessageController < ApplicationController
def create
if is_foo params[:text]
Foo.create
else
Bar.create
end
end
def is_foo(text)
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
My question is, do you think that it's a better design to put the "is_foo" logic inside the Foo model instead of the controller like so?
Model:
class Foo < ActiveRecord::Base
def self.is_foo(text)
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
Controller:
class MessageController < ApplicationController
def create
if Foo.is_foo params[:text]
Foo.create
else
Bar.create
end
end
end
On one hand, the model should take care of the logic. On the other, this isn't really logic, its more of an input rule... What do you think guys?
Helper
I'd leave the is_foo out of the model, as model logic should be to do with the model directly, not determining which model should be created / saved
I would personally look at using a helper method for the test - calling the file ControllerHelper or similar:
#app/helpers/controller_helper.rb
class ControllerHelper
def is_foo? text
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
This will allow you to call the helper in your controller, giving you the ability to use the logic to form the fixes:
#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
include ControllerHelper
def create
model = is_foo?(params[:text]) ? "foo" : "bar"
model.constantize.send(:create)
end
end
I wouldn't call it a ControllerHelper module as mentioned in Rich Pecks answer (since helpers in Rails are view-related), but something like
# app/lib/foo_bar_creator.rb
FooBarCreator = Struct.new(:params) do
def create
build.save
end
def build
klass.new
end
def is_foo?
params[:text] == 'foo'
end
def klass
is_foo? ? Foo : Bar
end
end
(some call these kind of classes "Service Objects")
This way I could just call FooBarCreator.new(params).create in my controller.

Accessing a variable declared in a controller from a model

I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end

Resources