Reference dynamic to groovy closure - grails

I have to do a refactoring in an advanced search method with 500 lines. I split this method using closures in small parts, and now I have a lot of closures but I want to invoke them dynamically.
For example:
def listCriteria={ ... }
def textCriteria={ ... }
def booleanCriteria={ ... }
criteria.listDistinct {
criteries.eachWithIndex { crit, i->
def criteriaType="${crit.type}Criteria"
...
}
}
How can I do that?

Using methods you could dynamically call this methods from a string like this:
def listCriteria() {"list"}
def textCriteria() {"text"}
def string1 = "list"
def string2 = "text"
assert "${string1}Criteria"() == "list"
assert "${string2}Criteria"() == "text"
Edit:
I don't know an elegant way to get a dynamic reference to a closure.
You could use the properties property of your controller class to find all closures and invoke them.
def allClosures = this.properties.findAll{Closure.isAssignableFrom(it.value.getClass())}
def callCriteriaClosureByName(name) {
def criteriaClosure = allClosures.find{it.key == "${name}Criteria"}.value
if(criteriaClosure)
criteriaClosure()
}
Not that nice - but should work.

Closures are good for scoping. What about using a map?
class Criteria {
def listDistinct(closure) {
closure()
}
}
closures = [
listCriteria : { "list" },
textCriteria : { "text" },
booleanCriteria : { "boolean" }
]
def criteries = ["list", "text", "boolean"]
def criteria = new Criteria()
criteria.listDistinct {
criteries.eachWithIndex { crit, index ->
def criteriaType=closures["${crit}Criteria"]
assert criteriaType instanceof Closure
}
}

Related

How can I build a GQL from a ruby hash?

I am building a rspec helper to test my graphql requests.
So far this is my helper:
def mutation_params(name, attributes:, return_types:)
{
query:
<<~GQL
mutation {
#{name}(
input: { attributes: #{attributes} })
#{return_types}
}
GQL
}
end
and I have to declare the attributes like this:
let(:attributes) do
<<~GQL
{
email: "#{email_param}",
password: "#{password_param}"
}
GQL
end
Now I want to know what I can do to be able to simply pass my arguments as a hash, and have the mutations_params method build the GQL from that hash, by iterating over them.
let(:attributes) do
{
email: email_param,
password: password_param
}
end
Something like:
def mutation_params(name, attributes:, return_types)
gql_attributes = <<~GQL
{
}
GQL
attributes.each do |key, value|
gql_attributes merge with
<<~GQL
"#{key}": "#{value}"
GQL
end
{
query:
<<~GQL
mutation {
#{name}(
input: { attributes: #{gql_attributes} })
#{return_types}
}
GQL
}
end
but that obviously does not work. I think my problem is I don't really understand what that <<~GQL is and how to manipulate it.
You're looking for the squiggly heredoc which was introduced in Ruby 2.3. It's like a normal heredoc but it leaves off leading indentation. https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html
So in other words, it's just a string! The GQL bit is arbitrary but a nice way of communicating the purpose of the heredoc.
You could write a helper like this to turn hashes into GraphQL strings
def hash_to_mutation(hash)
attr_gql_str = attributes.map{|k,v| "#{k}: #{v.inspect}"}.join(", ")
" { #{attr_gql_str} } "
end
Then assuming attributes is a hash as in your example you could just
def mutation_params(name, attributes:, return_types:)
{
query:
<<~GQL
mutation {
#{name}(
input: { attributes: #{hash_to_gql(attributes)} })
#{return_types}
}
GQL
}
end

How to use variables in GraphQL mutation for bulk adjust inventory?

I trying to bulk adjust inventory item of my Shopify product variants as explained in this article: https://www.shopify.com/partners/blog/multi-location_and_graphql
I tried hardcoding the variants ID in the query and it worked great :
<<-'GRAPHQL'
mutation {
inventoryBulkAdjustQuantityAtLocation(
locationId: "gid://shopify/Location/5537988719",
inventoryItemAdjustments: [
{inventoryItemId: "gid://shopify/InventoryItem/21112836292719", availableDelta: 1},
{inventoryItemId: "gid://shopify/InventoryItem/21112836325487", availableDelta: 10}
]) {
inventoryLevels {
available
}
}
}
GRAPHQL
Now I am trying to set the product variants ID as variables like follow:
require "graphql/client"
require "graphql/client/http"
class HomeController < ApplicationController
API_KEY = 'XXXXXX'.freeze
PASSWORD = 'XXXXXX'.freeze
SHARED_SECRET = 'XXXXXX'.freeze
SHOP_NAME = 'xxxxxx'.freeze
API_VERSION = '2019-04'.freeze
shop_url = "https://#{API_KEY}:#{PASSWORD}##{SHOP_NAME}.myshopify.com/admin"
ShopifyAPI::Base.site = shop_url
ShopifyAPI::Base.api_version = API_VERSION
CLIENT = ShopifyAPI::GraphQL.new
BULK_ADJUST = CLIENT.parse <<-'GRAPHQL'
mutation inventoryBulkAdjustQuantityAtLocation($inventoryItemAdjustments: [InventoryAdjustItemInput!]!, $locationId: ID!) {
inventoryBulkAdjustQuantityAtLocation(inventoryItemAdjustments: $inventoryItemAdjustments, locationId: $locationId) {
inventoryLevels {
id
}
userErrors {
field
message
}
}
}
GRAPHQL
def bulk_update_inventory
inventoryItemAdjustments = [
{ "inventoryItemId" => "gid://shopify/InventoryItem/1234", "availableDelta" => 1 },
{ "inventoryItemId" => "gid://shopify/InventoryItem/5678", "availableDelta" => 10 }
]
variables = {
"inventoryItemAdjustments" => inventoryItemAdjustments,
"locationId" => "gid://shopify/Location/9012"
}
result = CLIENT.query(BULK_ADJUST,
variables: variables)
render :json => { :result => result }
end
end
When I try to run the query I reach the following error:
Unknown action
The action 'bulk_update_inventory' could not be found for HomeController
There is anybody knows why do I have this error?
Finally got the answer!
The correct query was:
BULK_ADJUST = CLIENT.parse <<-'GRAPHQL'
mutation($inventoryItemAdjustments: [InventoryAdjustItemInput!]!, $locationId: ID!) {
inventoryBulkAdjustQuantityAtLocation(inventoryItemAdjustments: $inventoryItemAdjustments, locationId: $locationId) {
inventoryLevels {
id
}
userErrors {
field
message
}
}
}
GRAPHQL
The word "inventoryBulkAdjustQuantityAtLocation" after the "mutation" keyword had to be removed.
check your routes file and make sure you set one up for that special path.

How to modify to_json method and add a dynamic property to it - Rails 4

so i have this controller and i want to add a dynamic attribute along with the other data in the #events instance variable
i have search and tried things like #events.attributes.merge(appointment: true)
appointment = true is what i want to add to the events object.
def find
params = event_params
current_user = 2
#events = Event.where('date LIKE ?',"%#{params[:month]}%")
def #events.as_json(options = { })
h = super(options)
h[:appointments] = false # Or combine with above h[:appointments] = self.appointments?
h
end
respond_to do |format|
if current_user == 1
if #events
format.json {
render json: #events.to_json
}
else
render 'index'
end
else
format.json {
render json: #events.to_json
}
end
end
end
ajax code here
function retrieve(date_partial) {
var jsondata = {
events: {
month: date_partial,
}
}
$.ajax({
cache: false,
type: "POST",
url: "/events/find",
data: jsondata,
success: function(data) {
console.log(data);
for (var i = 0; i < data.length; i++) {
var day = data[i].date.substring(0, 2);
$("td[data-day='" + day + "']").addClass('added');
}
},
error: function(xhr) {
alert("The error code is: " + xhr.statusText);
}
});
so how can i add that property?
This could work ? But then maybe the JSON output isn't what you expected ?
format.json { render :json => {events: #events, appointments: true} }
Because this property is view oriented, the model should not know about it. A better way to do this, is to use a decorator, which will allow you to add what ever attributes you want in the manner you want, without polluting the model.
you can create a PORO object
like this one
# this is by no means a complete implementation, but just for you
# to get the idea
class EventDecorator
# use ( delegate :event_attribute, to: :event ) to delegate
# all the event attributes and to be able to access them
# as if they were declared on the decorator itself
attr_reader :event
attr_accessor :appointment
def initialize(event)
#event = event
#appointment = false
end
def to_json
event.attributes.merge(appointment: appointment).to_json
end
end
a better way is to use the draper gem. You can find a good explanation in this railscat, #286 Draper
Two ways to do that I can think of: adding an instance variable or a custom method (or something hybrid)
EDIT : Forget what I said about creating an instance variable out of nowhere (see this answer)^^"
Method
#events.define_singleton_method(:appointments?){true/false}
#events.appointments? # => true/false
EDIT 2 : AJAX/JSON override
See this answer
def #events.as_json(options = { })
h = super(options)
h[:appointments] = true/false # Or combine with above h[:appointments] = self.appointments?
h
end

How do I stub a method of an instance only if a specific instance variable has a value?

I have an object MyObject:
class MyObject
def initialize(options = {})
#stat_to_load = options[:stat_to_load] || 'test'
end
def results
[]
end
end
I want to stub the results method only if stat_to_load = "times". How can I do that? I tried:
MyObject.any_instance.stubs(:initialize).with({
:stat_to_load => "times"
}).stubs(:results).returns(["klala"])
but it does not work. Any idea?
So, I think there is probably a simpler way to test what you're trying to test, but without more context I don't know what to recommend. However, here is some proof-of-concept code to show that what you want to do can be done:
describe "test" do
class TestClass
attr_accessor :opts
def initialize(opts={})
#opts = opts
end
def bar
[]
end
end
let!(:stubbed) do
TestClass.new(args).tap{|obj| obj.stub(:bar).and_return("bar")}
end
let!(:unstubbed) { TestClass.new(args) }
before :each do
TestClass.stub(:new) do |args|
case args
when { :foo => "foo" }
stubbed
else
unstubbed
end
end
end
subject { TestClass.new(args) }
context "special arguments" do
let(:args) { { :foo => "foo" } }
its(:bar) { should eq "bar" }
its(:opts) { should eq({ :foo => "foo" }) }
end
context "no special arguments" do
let(:args) { { :baz => "baz" } }
its(:bar) { should eq [] }
its(:opts) { should eq({ :baz => "baz" }) }
end
end
test
special arguments
bar
should == bar
opts
should == {:foo=>"foo"}
no special arguments
bar
should == []
opts
should == {:baz=>"baz"}
Finished in 0.01117 seconds
4 examples, 0 failures
However I'm making a lot of use of special subject/let context blocks here. See http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/ for more on that subject.
Try out below, this should work as expected:
Here, Basically we are actually stubbing new instance getting created and also stubbing results method of the instance which is getting returned.
options = {:stat_to_load => "times"}
MyObject.stubs(:new).with(options)
.returns(MyObject.new(options).stubs(:results).return(["klala"]))
You could use plain old Ruby inside your test to achieve this.
MyObject.class_eval do
alias_method :original_results, :results
define_method(:results?) do
if stats_to_load == "times"
["klala"]
else
original_results
end
end
end

How can I dry helper methods declarations

I have a lot of helpers defined which all basically do the same.
def subtitle(page_subtitle)
content_for(:subtitle) { page_subtitle }
end
def header(page_header)
content_for(:header) { page_header }
end
def auto_header(page_auto_header)
content_for(:auto_header) { page_auto_header }
end
def header_image(page_header_image)
content_for(:header_image) { page_header_image }
end
def bodyclass(page_bodyclass)
content_for(:bodyclass) { page_bodyclass }
end
And there are many more...
My question is how can I DRY this code?
I tried something this but I didn't work
content_for_helpers = ["title","subtitle","logocolor"]
content_for_helpers.each do |helper|
def helper(helper)
content_for(helper.parameterize.underscore.to_sym) { helper }
end
end
def helper what
content_for(what) { send "page_#{what}" }
end

Resources