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
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
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.
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
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
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