Rails: Access cookies in a class in a helper - ruby-on-rails

I have a ruby class in a rails helper and inside that class I cannot access the ActiveController cookies method. I have looked at this question and it doesn't solve my problem and I'm not doing what that guy is since my ruby class is in the scope of the controller and is only ever called by it. What's the best way to get rid of the
NameError (undefined local variable or method `cookies` for #<CartHelper::CartObject:...>):
app/helpers/cart_helper.rb:##:in `save`
app/helpers/cart_helper.rb:##:in `add_product`
app/controllers/cart_controller.rb:##:in `add`
in my helper? (I'm also open to suggestions on how to make this more Ruby-y approach wise)
module CartHelper
class CartObject
def load (cart)
# converts from json
end
private def save
cookies.signed.permanent[:cart] = dump
end
private def dump
# converts to json
end
def add_product (product)
# ...
save
end
def remove_product (product)
# ...
save
end
end
def get_cart
return CartObject.new if cookies.signed.permanent[:cart].nil?
CartObject.load(cookies.signed.permanent[:cart])
end
end
and CartHelper is included in the application controller of course
class CartController < ApplicationController\
skip_before_action :verify_authenticity_token, only: [:add]
def view
cart = get_cart
#products = cart.products
end
def add # only by ajax
cart = get_cart
cart.add_product(Oj.strict_load(params[:product]))
head 200
end
def remove
cart = get_cart
cart.remove_product(params['rem'])
flash['success'] = 'Product removed.'
redirect_to '/cart'
end
end

Related

Should static pages have access to cookies in rails controller and routes.rb?

I'm sure this is an obvious question but I just don't understand why this isn't working.
I'm finding that my static pages defined in the routes.rb file don't seem to have access to cookies? Is that correct? I'm trying to read the value of a cookie but the pages below seem to return a null object.
My routes.rb contains the following:
scope controller: :static do
get :about
get :terms
get :privacy
get :cookies
get :returns
get :delivery
end
For completeness here is the Static Controller:
# frozen_string_literal: true
class StaticController < ApplicationController
before_action :find_order
def index; end
def about; end
def pricing
redirect_to root_path, alert: t('.no_plans') unless Plan.without_free.exists?
end
def terms; end
def privacy; end
def cookies; end
def returns; end
def delivery; end
end
And this is how the cookie is set:
class OrdersController < ApplicationController
before_action :find_order
def add_hamper
#order ||= Order.create!(
ordernum: find_next_order_number,
user: current_user,
basket: true
)
#order.hampers << Hamper.friendly.find(params[:id])
update_order_total(#order)
if current_user&.custaddress
#order.update(custaddress: current_user.custaddress)
else
cookies[:ordernum] = #order.ordernum
end
redirect_to order_path(#order.ordernum)
# Update basket in Navbar
# Save the information as a cookie reference if they are not signed in
end
At the start of each controller I have a before_action to find an order if it exists in the DB or Cookie. For all other controllers, the find_order method is working. For the StaticController, there seems to be no access to the cookies.
Here is my find_order as defined in ApplicationController:
def find_order
#order = if current_user
Order.where(
user: current_user,
basket: true
).first
else
if cookies.dig[:ordernum]
Order.where(
ordernum: cookies[:ordernum],
basket: true
).first
end
end
end
helper_method :find_order
I've had to add the check for cookies and then if cookies[:ordernum] to stop it from failing on the static pages.
Thanks for any help with this.
PS. If anyone feels the above code could be better ... please let me know! There must be a nicer way to achieve this. It feels a little clunky.

Rails early return if before_action attribute set fails

I would like to perform a db lookup using the incoming request's remote_ip before any controller method is hit in order to set a particular controller class attribute. However if the lookup fails (if no object is linked to the request IP) I would like to immediately return a 404 response.
For example:
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
# or if no instance is found, immediately return a 404 without hitting "index" method (or any other method for that matter)
end
end
Any help will be greatly appreciated!
You can raise an ActiveRecord::RecordNotFound exception, which will stop the action and return a 404. Or you can render or redirect which will also stop the action. See Rails Filters docs. Here are examples of each.
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
raise ActiveRecord::RecordNotFound unless #instance # returns 404
end
end
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
render(status: 404, inline: "Instance not found") unless #instance
end
end

call service method from a controller following correct factory patterns

I have the following class
class EvaluateService
def initialize
end
def get_url
end
def self.evaluate_service
#instance ||= new
end
end
class CheckController < ApplicationController
def index
get_url = EvaluateService.get_url
end
end
The problem here is that i know that i can do evaluate_service = EvaluateService.new and use the object evaluate_service.get_url and it will work fine but i also know that some frown upon the idea of initializing the service object this way and rather there is a way of initializing it via a call, send method in the service class.
Just wondering how do i do this?
I think what you're looking for is something like:
class Evaluate
def initialize(foo)
#foo = foo
end
def self.call(foo)
new(foo).call
end
def call
url
end
private
def url
# Implement me
end
end
Now you can do this in your controller:
class CheckController < ApplicationController
def index
#url = Evaluate.call(params)
end
end
The reason some prefer #call as the entry point is that it's polymorphic with lambdas. That is, anywhere you could use a lambda, you can substitute it for an instance of Evaluate, and vice versa.
There are various ways to approach this.
If the methods in EvaluateService don't need state, you could just use class methods, e.g.:
class EvaluateService
def self.get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.get_url
end
end
In this scenario, EvaluateService should probably be a module.
If you want a single global EvaluateService instance, there's Singleton:
class EvaluateService
include Singleton
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.instance.get_url
end
end
But global objects can be tricky.
Or you could use a helper method in your controller that creates a service instance (as needed) and memoizes it:
class EvaluateService
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = evaluate_service.get_url
end
private
def evaluate_service
#evaluate_service ||= EvaluateService.new
end
end
Maybe even move it up to your ApplicationController.

How to access class method from instance method in ruby on rails non activerecord model

I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end

How to generate an array of controller actions in Ruby on Rails?

In my Rails 4 app I have a number of static pages that should either be indexable by Google or not. I am using a variable indexable for this but there's probably a better way:
class PagesController < ApplicationController
def home
indexable = true
end
def about_us
indexable = true
end
def secret_stuff
indexable = false
end
end
How can I generate an array of all the pages that are indexable?
I tried doing this in a helper but it's not working:
def indexable_pages
array = []
PagesController.instance_methods(false).each do |action|
if action.indexable == true # this won't work of course
array << action
end
end
array
end
Thanks for any help.
Maybe a before_filter would make sense?
class PagesController < ApplicationController
before_filter :set_indexable, except: [:secret_stuff]
def home
end
def about_us
end
def secret_stuff
end
private
def set_indexable
#indexable = true
end
end

Resources