undefined method `stringify_keys' for Object - ruby-on-rails

I am fairly new to the world of Ruby and Rails (started a week ago, my background is mostly PHP). I've got this class here which acts as a factory.
I get the following error :
undefined method `stringify_keys' for #
I understand the constructor (.new) expects a Hash instead of an object (why ?) but, although I spent a couple of hours searching the www, I didn't come up with a viable solution at this point.
I just want to inject that Soap object into my constructor. That constructor is pretty straightforward, it puts the object parameter in the instance variable that is supposed to store it.
I've been looking for methods that would turn that object into a proper Hash but all I saw was a bunch of OLD posts with rather dirty hacks. I'd rather quit programming than use them ^^.
I never thought doing this would cause a headache...
Thanks for the tips !
class WebServices::WebServiceFactory
def initialize (type, url, login, password, protocol = "soap")
#type, #protocol, #url, #login, #password = type, protocol, url, login, password
case #protocol.capitalize
when "Soap" then
requestor = WebServices::Soap::Soap.new(url, login, password)
end
#class = #type.constantize.new(requestor)
end
def getservice
return #class
end
end

The initialize method can accept whatever arguments you define, it doesn't need to be a Hash, and in fact, the code above it looks like you have 5 arguments that can be of any type.
Are you wanting to pass the Soap object as an argument of initialize?
If you are, something like this would do (assuming you are using your version of Ruby is >= 2.0)
class WebServices::WebServiceFactory
def initialize(requestor:, type:)
#class = type.constantize.new(requestor)
end
end
If you want this class to handle the details of creating the Soap object, maybe something like this would work for you:
module WebServices
class WebServiceFactory
SERVICES = {
soap: 'WebServices::Soap::Soap'
}
def initialize(type:, protocol: :soap, params: {})
return unless SERVICES.key?(protocol.to_sym)
requestor = SERVICES[protocol.to_sym].constantize.new(params[:url], params[:login], params[:password])
#class = type.constantize.new(requestor)
end
end
end

Related

Naming getter method with 'get_' when attribute is already present

I'm attempting to restrict an API's content type in a RoR application, with a method that gets inherited by all controllers.
CONTENT_TYPE = 'application/vnd.api+json'
def restrict_content_Type
return if request.content_type = CONTENT_TYPE
render_content_type_error
end
this works fine, but now I must use a different content type for a single endpoint and controller, and I'd like to just change the content of the CONTENT_TYPE constant while reusing the code I already have. To use a different constant I must use a reader method that looks up the constant in the current controller.
I refactored the code into:
def get_content_type
self::CONTENT_TYPE
end
def restrict_content_type
return if request.content_type == get_content_type
...
end
The reason I've used a get_* reader is that self.content_type returns the Request's content type: https://api.rubyonrails.org/classes/ActionDispatch/Response.html#method-i-content_type
At this point Rubocop is complaining because of the name I used, get_*readers are not idiomatic Ruby.
I can surely override this behaviour in rubocop but I'd like to hear what are my other options and if there are other solutions, because I don't like the name of the method either.
Any idea?
You can use some other names that reveals the purpose of this this method, e.g. current_content_type, restricted_content_type, disabled_content_type - whatever suits you best.
About the naming it could be nice to have a method called invalid_content_type? which returns a Boolean.
For e.g :
def invalid_content_type?(content_type)
request.content_type == content_type
end
def restrict_content_type
return if invalid_content_type(self::CONTENT_TYPE)
...
end

implementing custom authentication with devise, 'mapping' member

I'm trying to write a custom (remote) authentication for devise.
All the API doc I've found is this example, so I'm proceding by trials and errors.
I'm particularly interested in understanding what does the 'mapping.to.new' line do.
It seems to be crucial since if it returns nil, the authentication process will fail.
But what are those "mappings", where are they defined?
Furthermore, the call
mapping.to.new
has something strange, it seems like an object instantiation... isn't it?
I've also found a different implementation, that looks like:
resource = mapping.to.where(["username = ?", auth_params[:username]]).first
where it seems that mapping.to returns a relation object, but again, where am i expected to define what my mappings are?
class RemoteAuthenticatable < Authenticatable
def authenticate!
auth_params = authentication_hash
auth_params[:password] = password
resource = mapping.to.new
return fail! unless resource
if validate(resource){ resource.remote_authentication(auth_params) }
success!(resource)
end
end
end
mapping.to is instance to class of model on which you map authentification ( often is a User or Admin).
So if you call mapping.to.new is the same as you call User.new.
And if you call mapping.to.where(...) it return same result as User.where(...).
More you can find on mapping.rb

Ruby 2 Keyword Arguments and ActionController::Parameters

I have a rails 4 application that is running on ruby 2.1. I have a User model that looks something like
class User < ActiveModel::Base
def self.search(query: false, active: true, **extra)
# ...
end
end
As you can see in the search method I am attempting to use the new keyword arguments feature of ruby 2.
The problem is that when I call this code from in my controller all values get dumped into query.
params
{"action"=>"search", "controller"=>"users", query: "foobar" }
Please note that this is a ActionController::Parameters object and not a hash as it looks
UsersController
def search
#users = User.search(params)
end
I feel that this is because params is a ActionController::Parameters object and not a hash. However even calling to_h on params when passing it in dumps everything into query instead of the expected behavior. I think this is because the keys are now strings instead of symbols.
I know that I could build a new hash w/ symbols as the keys but this seems to be more trouble than it's worth. Ideas? Suggestions?
Keywords arguments must be passed as hash with symbols, not strings:
class Something
def initialize(one: nil)
end
end
irb(main):019:0> Something.new("one" => 1)
ArgumentError: wrong number of arguments (1 for 0)
ActionController::Parameters inherits from ActiveSupport::HashWithIndifferentAccess which defaults to string keys:
a = HashWithIndifferentAccess.new(one: 1)
=> {"one"=>1}
To make it symbols you can call symbolize_keys method. In your case: User.search(params.symbolize_keys)
I agree with Morgoth, however, with rails ~5 you will get a Deprecation Warning because ActionController::Parameters no longer inherits from hash. So instead you can do:
params.to_unsafe_hash.symbolize_keys
or if you have nested params as is often the case when building api endpoints:
params.to_unsafe_hash.deep_symbolize_keys
You might add a method to ApplicationController that looks something like this:
def unsafe_keyworded_params
#_unsafe_keyworded_params ||= params.to_unsafe_hash.deep_symbolized_keys
end
You most likely do need them to be symbols. Try this:
def search
#users = User.search(params.inject({}){|para,(k,v)| para[k.to_sym] = v; para}
end
I know it's not the ideal solution, but it is a one liner.
In this particular instance I think you're better off passing the params object and treating it as such rather than trying to be clever with the new functionality in Ruby 2.
For one thing, reading this is a lot clearer about where the variables are coming from and why they might be missing/incorrect/whatever:
def search(params)
raise ArgumentError, 'Required arguments are missing' unless params[:query].present?
# ... do stuff ...
end
What you're trying to do (in my opinion) only clouds the issue and confuses things when trying to debug problems:
def self.search(query: false, active: true, **extra)
# ...
end
# Method explicitly asks for particular arguments, but then you call it like this:
User.search(params)
Personally, I think that code is a bit smelly.
However ... personal opinion aside, how I would fix it would be to monkey-patch the ActionController::Parameters class and add a #to_h method which structured the data as you need it to pass to a method like this.
Using to_unsafe_hash is unsafe because it includes params that are not permitted. (See ActionController::Parameters#permit) A better approach is to use to_hash:
params.to_hash.symbolize_keys
or if you have nested params:
params.to_hash.deep_symbolize_keys
Reference: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash

How to transform a string into a variable/field?

I'm new to Ruby and I would like to find out what the best way of doing things is.
Assume the following scenario:
I have a text field where the user can input strings. Based on what the user inputs (after validation) I would like to access different fields of an instance variable.
Example: #zoo is an instance variable. The user inputs "monkey" and I would like to access #zoo.monkey. How can I do that in Ruby?
One idea that crossed my mind is to have a hash:
zoo_hash = { "monkey" => #zoo.monkey, ... }
but I was wondering if there is a better way to do this?
Thanks!
#zoo.attributes gives you a hash of the object attributes. So you can access them like
#zoo.attributes['monkey']
This will give nil if the attribute is not present. Calling a method which doesn't exist will throw NoMethodError
In your controller you could use the public_send (or even send) method like this:
def your_action
#zoo.public_send(params[:your_field])
end
Obviously this is no good, since someone can post somehing like delete_all as the method name, so you must sanitize the value you get from the form. As a simple example:
ALLOWED_METHODS = [:monkey, :tiger]
def your_action
raise unless ALLOWED_METHODS.include?(params[:your_field])
#zoo.public_send(params[:your_field])
end
There is much better way to do this - you should use Object#send or (even better, because it raises error if you try to call private or protected method) Object#public_send, like this:
message = 'monkey'
#zoo.public_send( message )
You could implement method_missing in your class and have it interrogate #zoo for a matching method. Documentation: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-method_missing
require 'ostruct' # only necessary for my example
class ZooKeeper
def initialize
#zoo = OpenStruct.new(monkey: 'chimp')
end
def method_missing(method, *args)
if #zoo.respond_to?(method)
return #zoo.send(method)
else
super
end
end
end
keeper = ZooKeeper.new
keeper.monkey #=> "chimp"
keeper.lion #=> NoMethodError: undefined method `lion'

Is it possible to use the 'request' method in a model?

I am using Ruby on Rails 3 and I am tring to do some like this in my model file
if request.headers["CONTENT_LENGTH"]
...
end
but I get this error:
NameError (undefined local variable or method `request' for #<User:0x00...>):
So, is it possible to use the 'request' method in a model? If so, how?
No, because the request is only available in controllers and view code. From a design point of view you're ill advised to use the request within model code, but let's say you want to do something with the request on a particular instance of your User, just create a method for it:
class User
...
def has_a_request?(request)
raise ArgumentError if(request.blank? || !request.respond_to(:headers))
if(request.headers["CONTENT_LENGTH"] # Does it really have CONTENT_LENGTH btw?
puts "#{self.username} got a request with content length"
return true
else
puts "#{self.username} didn't get a request with content length"
return false
end
end
And then elsewhere in your application:
User.has_a_request?(request)
Is not good app design to use the request object in the method, but if you do absolutely need it you can do :
class User
def a_method request
end
end
class UserController
def index
User.find(params[:user]).a_method request
end
end
Extract relevant data from the request object in your controller, then pass them on to the model -- either by setting attributes on an instance of it or by passing them as parameters to a method call.

Resources