How to use rails Helpers in graphql? - ruby-on-rails

How can I use Helpers from /app/helpers directory in my /app/graphql directory? For example there is a data type which is a nested JSON Object and I got a JSON Schema file which describes the structure of it. There also is a JsonSchemaHelper which I would like to use to validate a Scalar type against the JSON schema. like that:
class Types::Scalar::Node < Types::Base::BaseScalar
def self.coerce_input(value, _context)
if Validators::GraphqlValidator.is_parsable_json?(value)
value = JSON.parse(value)
end
Validators::Node.validate!(value)
value
end
#Validators could be used to check if it fit the client-side declared type
def self.coerce_result(value, _context)
Validators::Node.validate!(value)
value
end
end
and the validator looks like:
module Validators
class Node
include JsonSchemaHelper
def self.validate!(ast)
json_schema_validate('Node', ast)
end
end
end
the include JsonSchemaHelper doesn't work.

include adds methods of JsonSchemaHelper as instance methods of Validators::Node class. self.validate!(ast) is a class method and you try to call json_schema_validate as a class method. Change include JsonSchemaHelper to extend JsonSchemaHelper.

Related

Add method to a class which can only be accessed inside specific class

I have a class in initializers in which I use Hash class and I would like to add 2 methods to Hash class. I know how to add methods to the class but I don't want to make the Hash class "dirty".
Is there a way that I can extend the Hash class with those two methods but only inside the class where I use them?
You could use refinements for this:
Due to Ruby's open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.
Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a class locally. Refinements can modify both classes and modules.
Something like this:
module HashPatches
refine Hash do
def new_hash_method
# ...
end
end
end
and then:
class YourClass
using HashPatches
def m
{}.new_hash_method
end
end
That would let you call YourClass.new.m (which would use new_hash_method) but it wouldn't pollute Hash globally so outside YourClass, some_hash.new_hash_method would be a NoMethodError.
Reading:
Official Refinements docs
Refinements spec
A less hacky way could be to use SimpleDelegator.
class Foo
class SuperHash < SimpleDelegator
def new_method
# do something with hash
# you can use __getobj__() or super
end
end
private_constant :SuperHash
def initialize
#hash = SuperHash.new({})
end
end
https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html

In Rails, where to put useful functions for both controllers and models

Suppose I have a function trim_string(string) that I want to use throughout my Rails app, in both a model and a controller. If I put it in application helper, it gets into the controller. But application helper isn't required from within models typically. So where do you put common code that you'd want to use in both models and controllers?
In answer to the specific question "where do you put common code that you'd want to use in both models and controllers?":
Put it in the lib folder. Files in the lib folder will be loaded and modules therein will be available.
In more detail, using the specific example in the question:
# lib/my_utilities.rb
module MyUtilities
def trim_string(string)
do_something
end
end
Then in controller or model where you want this:
# models/foo.rb
require 'my_utilities'
class Foo < ActiveRecord::Base
include MyUtilities
def foo(a_string)
trim_string(a_string)
do_more_stuff
end
end
# controllers/foos_controller.rb
require 'my_utilities'
class FoosController < ApplicationController
include MyUtilities
def show
#foo = Foo.find(params[:id])
#foo_name = trim_string(#foo.name)
end
end
It looks like you want to have a method on the String class to "trim" itself better than a trim_string function, right? can't you use the strip method? http://www.ruby-doc.org/core-2.1.0/String.html#method-i-strip
You can add new methods to the string class on an initializer, check this In Rails, how to add a new method to String class?
class String
def trim
do_something_and_return_that
end
def trim!
do_something_on_itself
end
end
That way you can do:
s = ' with spaces '
another_s = s.trim #trim and save to another
s.trim! #trim itself
but check the String class, it looks like you already have what you need there

How do I add a model specific configuration option to a rails concern?

I'm in the process of writing an Importable concern for my rails project. This concern will provide a generic way for me to import a csv file into any model that includes Importable.
I need a way for each model to specify which field the import code should use to find existing records. Are there any recommended ways of adding this type of configuring for a concern?
A slightly more "vanilla-looking" solution, we do this (coincidentally, for the exactly some csv import issue) to avoid the need for passing arguments to the Concern. I am sure there are pros and cons to the error-raising abstract method, but it keeps all the code in the app folder and the models where you expect to find it.
In the "concern" module, just the basics:
module CsvImportable
extend ActiveSupport::Concern
# concern methods, perhaps one that calls
# some_method_that_differs_by_target_class() ...
def some_method_that_differs_by_target_class()
raise 'you must implement this in the target class'
end
end
And in the model having the concern:
class Exemption < ActiveRecord::Base
include CsvImportable
# ...
private
def some_method_that_differs_by_target_class
# real implementation here
end
end
Rather than including the concern in each model, I'd suggest creating an ActiveRecord submodule and extend ActiveRecord::Base with it, and then add a method in that submodule (say include_importable) that does the including. You can then pass the field name as an argument to that method, and in the method define an instance variable and accessor (say for example importable_field) to save the field name for reference in your Importable class and instance methods.
So something like this:
module Importable
extend ActiveSupport::Concern
module ActiveRecord
def include_importable(field_name)
# create a reader on the class to access the field name
class << self; attr_reader :importable_field; end
#importable_field = field_name.to_s
include Importable
# do any other setup
end
end
module ClassMethods
# reference field name as self.importable_field
end
module InstanceMethods
# reference field name as self.class.importable_field
end
end
You'll then need to extend ActiveRecord with this module, say by putting this line in an initializer (config/initializers/active_record.rb):
ActiveRecord::Base.extend(Importable::ActiveRecord)
(If the concern is in your config.autoload_paths then you shouldn't need to require it here, see the comments below.)
Then in your models, you would include Importable like this:
class MyModel
include_importable 'some_field'
end
And the imported_field reader will return the name of the field:
MyModel.imported_field
#=> 'some_field'
In your InstanceMethods, you can then set the value of the imported field in your instance methods by passing the name of the field to write_attribute, and get the value using read_attribute:
m = MyModel.new
m.write_attribute(m.class.imported_field, "some value")
m.some_field
#=> "some value"
m.read_attribute(m.class.importable_field)
#=> "some value"
Hope that helps. This is just my personal take on this, though, there are other ways to do it (and I'd be interested to hear about them too).

How to dynamically generate association names?

I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):
# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.
class Article < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
class ModelAsLikeArticle < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:
class Article < ActiveRecord::Base
include MyModule
end
class ModelAsLikeArticle < ActiveRecord::Base
include MyModule
end
module MyModule
def my_squeel_query
commenters.
.where{
# Note: This code doesn't work. It is just an sample.
"#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
}
end
end
But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.
How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?
Since the DSL is instance_evaled, you can actually say something like:
def my_squeel_query
base = self
commenters.
.where{
# Note: This code does work. Because it's awesome.
__send__("#{base.class.to_s.singularize}_comment_associations").
article_id.eq(my{self.id})
}
end
You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:
module ModuleAsLikeArticle
def self.included(base)
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.
As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.
Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:
module ModuleAsLikeArticle
def has_comments
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This would be used like this:
class ModelAsLikeArticle < ActiveRecord::Base
extend MyModule
has_comments
end
Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.
I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.

What should I not include in the `included do ... end` block?

I am using Ruby on Rails 3.2.2. I am implementing a module and including that in a my class by using the RoR ActiveSupport::Concern feature. It makes available the included do ... end block making code stated inside that to be executed in the class context of the class where the module is included.
My doubt is: What should I not include in the included do ... end block? That is, for instance, is it a "common" / "good" practice to make the following?
module MyModule
extend ActiveSupport::Concern
class MyModuleClass
attr_accessor :attr1, :attr2, :attr3
def initialize(attrs)
#attr1 = attrs[:attr1]
#attr2 = attrs[:attr2]
#attr3 = attrs[:attr3]
end
end
included do
#my_module_class = MyModuleClass.new(some_attrs)
end
end
More, will be the #my_module_class variable available as an attribute in the including class of MyModule (BTW: I would like to make the #my_module_class to be "visible" only internally to MyModule since it is intended to be used only in that module)? Are there some "advanced" examples or tutorials on how to handle situations like that I am trying to instantiate in the included do ... end block of the above code? What do you advice about?
#my_class will be an instance of MyClass and not MyModule. If you want to make all instances of MyClass be an instance of MyModule you should write:
include MyModule
inside the class definition.
I think my answer makes sense if you look at the original version of this question before it was edited.
EDIT 1:
Let's add on to your example and say you have a class called Foo:
class Foo
include MyModule
end
You want to make an instance of MyModuleClass that is associated with Foo but it sounds like you don't really want to modify Foo or give it access to the MyModuleClass. I propose that you use a hash table:
module MyModule
# ...
#hash = {}
class << self
attr_accessor :hash
end
included do
MyModule.hash[self] = MyModuleClass.new(some_attrs)
end
end
I think that will work, and it avoids adding an instance variable to to the Foo class object. Technically, any part of the ruby code can access MyModule.hash but you should put a comment in the source code telling people NOT to do that, and don't advertise that the hash exists.

Resources