More readable, DRY class with jira issue creation - ruby-on-rails

In my rails app I've got a base class - creator - which creates an issue in Jira board using jira-ruby gem. I want to use it in 15 different scenarios but the only thing that will be different are required fields such as summary, description, and issuetype. To avoid duplicates I use inheritance where in each case I've got a class with only one method - required_fields.
creator
module Jira
class Creator
def initialize(webhook)
#webhook = webhook
end
def call
issue = client.Issue.build
issue.save(ticket_class.new(webhook).required_fields)
end
private
def client
#client ||= Jira::JiraConnection.new.call
end
def ticket_class
#ticket_class ||= "Issues::#{webhook.action_type_class}".constantize"
end
def required_fields; end
end
end
Sample of class inheriting from Creator:
class NewRepo < Creator
def required_fields
{
'fields' => {
'summary' => 'Create new repo <github_repo> for <Github user>',
'description' => 'This is an automatic confirmation of creating new PRIVATE repo
- <github_repo> for <Github user>',
'project' => { 'key' => 'TEST' },
'issuetype' => { 'id' => '12580' },
'customfield_15100' => 'None'
}
}
end
end
This is required_fields is not so readable, how to create such an class where hash in required_fields will be look like:
{
'fields' => {
'summary' => new_repo,
'project' => testing_project,
'issuetype' => autoresolved,
'customfield_15100' => 'None'
}
}
How to put new_repo, testing_project etc (which will be a method probably?) inside of this hash? should I merge this somehow? Or maybe there is a better way to make this hash more readable?
Here are different scenarios for required fields which I want to use depends on webhook information:
class AddMember < Creator
def required_fields
{
'fields' => {
'summary' => 'Add <Github user> collaborator to <github_repo>',
'description' => 'This is an automatic ticket confirmation of user added',
'project' => { 'key' => 'New Board' },
'issuetype' => { 'id' => '12581' },
'customfield_15100' => 'None'
}
}
end
end
class DeleteRepo < Creator
def required_fields
{
'fields' => {
'summary' => 'Recheck <Github user> deleted <github_repo>',
'description' => 'This is an automatic ticket confirmation of delete repo <github_repo>',
'project' => { 'key' => 'Second Board' },
'issuetype' => { 'id' => '1234' },
'customfield_15100' => 'None'
}
}
end
end

Related

Ruby sort array of hashes by specific requirements

I need to sort array of hashes:
resource = [{ 'resource_id' => 34,
'description' => 'NR00123',
'total_gross_amount_cents' => bank_transfer.amount_cents,
'contractor_name' => 'Bogisich Inc' },
{ 'resource_id' => 35,
'description' => bank_transfer.purpose,
'total_gross_amount_cents' => 54321,
'contractor' => 'Bogisich Inc' },
{ 'resource_id' => 36,
'description' => 'Some description 2',
'total_gross_amount_cents' => 0123,
'contractor' => bank_transfer.creditor_name
}
]
By following requirements:
first - match_invoice_number
def match_invoice_number(resource)
bank_transfer.purpose&.include?(resource['description'])
end
second - match_amount
def match_amount(resource)
bank_transfer.amount_cents == resource['total_gross_amount'] || resource['gross_amount_cents']
end
third - match_vendor
def match_vendor(resource)
resource['contractor'].include?(bank_transfer.creditor_name)
end
So at the end resource should be like:
resource = [
{ 'resource_id' => 35,
'description' => bank_transfer.purpose,
'total_gross_amount_cents' => 54_321,
'contractor' => 'Bogisich Inc' },
{ 'resource_id' => 34,
'description' => 'NR00123',
'total_gross_amount_cents' => bank_transfer.amount_cents,
'contractor_name' => 'Bogisich Inc' },
{ 'resource_id' => 36,
'description' => 'Some description 2',
'total_gross_amount_cents' => 0o123,
'contractor' => bank_transfer.creditor_name }
]
I was trying to use select but the end resource looks the same like at the beginning. Here what I use:
def only_suggested(resource)
resource.select do |resource|
collection(resource)
end
end
def collection(resource)
[match_invoice_number(resource), match_amount(resource), match_vendor(resource)]
end
collection(resource) method returns an array which treated as true value when select checks it, so you get your entire collection back. To sort you can use sort_by method. If you need to uplift an item that follows all conditions use all? method:
resource.sort_by do |resource|
collection(resource).all? ? 0 : 1 # To return sortable value
end
If conditions have different priorities:
resource.sort_by do |resource|
if match_invoice_number(resource)
0
elsif match_amount(resource)
1
elsif match_vendor(resource)
2
else
3
end
end

Why isn't Oauth setting email in tests?

# spec/support/omniauth_helper.rb
module OmniauthMacros
def mock_auth_hash
OmniAuth.config.mock_auth[:github] = {
'provider' => 'github',
'uid' => '11',
'info' => {
'email' => 'gituser#github',
'name' => 'gituser',
'image' => 'mock_user_thumbnail_url'
},
'credentials' => {
'token' => 'mock_token',
'secret' => 'mock_secret'
}
}
end
end
This returns
ActiveRecord::RecordInvalid: Validation failed: Email can't be blank.
If I move this into Test environment config it starts working. Why is this happening?

Avoid duplicates to of new classes with the same code

I've got a class which create an issue in Jira board. I want to use it in 5 different scenarios but the only things that will be different are required fields such as summary, description, and issuetype. How to handle such a scenario to avoid creating few classes with 90% the same content?
This is a main content of the class:
module Jira
class TicketCreator
def call
issue = client.Issue.build
issue.save(required_fields)
end
private
def client
#client ||= Jira::JiraConnection.new.call
end
def required_fields
#data from below examples
end
end
end
Here are scenarios for required fields which I want to use depends on webhook information:
def required_fields
{
'fields' => {
'summary' => 'Create new repo <github_repo> for <Github user>',
'description' => 'This is an automatic confirmation of creating new PRIVATE repo
- <github_repo> for <Github user>',
'project' => { 'key' => 'TEST' },
'issuetype' => { 'id' => '12580' },
'customfield_15100' => 'None'
}
}
end
def required_fields
{
'fields' => {
'summary' => 'Add <Github user> collaborator to <github_repo>',
'description' => 'This is an automatic ticket confirmation of user added',
'project' => { 'key' => 'TEST' },
'issuetype' => { 'id' => '12580' }, # nonautoresolved
'customfield_15100' => 'None'
}
}
end
def required_fields
{
'fields' => {
'summary' => 'Recheck <Github user> deleted <github_repo>',
'description' => 'This is an automatic ticket confirmation of delete repo <github_repo>',
'project' => { 'key' => 'TEST' }, # change to project_key
'issuetype' => { 'id' => '12579' }, # autoresolved
'customfield_15100' => 'None'
}
}
end
How to avoid creation new classes where the only difference will be this required_fields method?
You can just use keyword arguments together with Hash#reverse_merge to merge the default options with passed options:
module Jira
class TicketCreator
# Do setup at initializion instead
def initialize
# FIXME - style violation - this should be client.issue.build
#issue = client.Issue.build
end
def call(**options)
#issue.save(required_fields(options))
end
# Takes care of initializion so that you can call
# Jira::TicketCreator.call - you can DRY this though inheritance or mixins
def self.call(**options)
new.call(options)
end
private
def client
#client ||= Jira::JiraConnection.new.call
end
def required_fields(**options)
{
'fields' => {
'summary' => 'Recheck <Github user> deleted <github_repo>',
'description' => 'This is an automatic ticket confirmation of delete repo <github_repo>',
'project' => { 'key' => 'SUP' }, # change to project_key
'issuetype' => { 'id' => '12579' }, # autoresolved
'customfield_15100' => 'None'
}.reverse_merge(options.deep_stringify_keys)
}
end
end
end
ticket = Jira::TicketCreator.call(
project: { key: 'TEST' },
issuetype: { id: '12580' }
)
Can you send the required_fields to your class?
# TicketCreator.new(required_fields).call
module Jira
class TicketCreator
def initialize(required_fields)
#required_fields = required_fields
end
def call
issue = client.Issue.build
issue.save(#required_fields)
end
private
def client
#client ||= Jira::JiraConnection.new.call
end
end
end
If you want a command for each one, you could use inheritance:
module Jira
class Base
def call
issue = client.Issue.build
issue.save(required_fields)
end
private
def client
#client ||= Jira::JiraConnection.new.call
end
def required_fields; end
end
end
module Jira
class CreateRepo < Base
def required_fields
{
'fields' => {
'summary' => 'Create new repo <github_repo> for <Github user>',
'description' => 'This is an automatic confirmation of creating new PRIVATE repo
- <github_repo> for <Github user>',
'project' => { 'key' => 'TEST' },
'issuetype' => { 'id' => '12580' },
'customfield_15100' => 'None'
}
}
end
end
end
module Jira
class AddCollaborator < Base
def required_fields
{
# data
}
end
end
end

How to use aws_account_utils to create an AWS account ? Getting error : cannot load such file -- aws_account_utils

I want to use AWSAccountUtils in my project to create AWS account. I have installed gem aws_account_utils too. What more do I need to do or what is that I am missing ? In my controller I have defined following function and code is :
def create_aws_account
require 'aws_account_utils'
#account_details = []
#account_values = {}
aws_utils = AwsAccountUtils::AwsAccountUtils.new()
details = { 'fullName' => 'Devanshu Kumar',
'company' => 'ABC',
'addressLine1' => 'CP, Bund Garden Road',
'city' => 'Pune',
'state' => 'Maharastra',
'postalCode' => '411007',
'phoneNumber' => '1234567890',
'guess' => 'Test Account Dev' }
resp = aws_utils.create_account(account_name: 'My Test Account Devanshu Kumar',
account_email: 'devanshu.kumar#abc.com',
account_password: 'password',
account_details: details)
#account_values = {:account_number => data_disk.resp}
#account_details.push #account_values
render :json => { created_aws_account: account_details }
end
AWS Account Details Error Image

How to use filters in Magento SOAP API v1 with Rails 4 and Savon?

I'm building an app in Rails 4 using the Magento SOAP API v1 and Savon gem. Right now I am trying to get all orders with a status of pending. To hook into the API I am using this code:
class MagentoAPI
def self.call method, options={}
response = ##soap_client.request :call do
if options.empty?
soap.body = { :session => ##soap_session, :method => method }
elsif options[:string]
soap.body = { :session => ##soap_session, :method => method, :arguments => [options[:string]] }
else
puts options
soap.body = { :session => ##soap_session, :method => method, :arguments => options }
end
end
if response.success?
# listing found products
final = []
call_return = response[:call_response][:call_return]
return [] if call_return[:item].nil?
raw = call_return[:item]
if raw.is_a? Hash # this is a list of one item
final << raw[:item].inject({}){|x,y| x.merge(y[:key]=>y[:value])}
else
if raw[0][:item].nil? # this is a product info
return raw.inject({}){|x,y| x.merge(y[:key]=>y[:value])}
else # this is a list of many items
raw.each{|result| final << result[:item].inject({}){|x,y| x.merge(y[:key]=>y[:value])}}
end
end
final
end
end
end
And then this:
class Order
def self.get_all_active
activeOrders = MagentoAPI.call 'order.list', :filter => {:status => 'pending'}
end
end
This just returns Savon::HTTP::Error so I'm thinking I'm not formatting the request properly. Does anybody have any experience or insight on this?
Hope this isn't too late (assume it might be), but I created a gem for this with some rudimentary documentation. I'm hoping to finish it up this weekend or next week, but you can take a look at the code and see how I'm creating the filters for Magento. To install, just run:
gem install magento_api_wrapper
To summarize, if you want to use one of the Magento SOAP API simple filters, you can pass a hash with a key and value:
api = MagentoApiWrapper::Sales.new(magento_url: "yourmagentostore.com/index.php", magento_username: "soap_api_username", magento_api_key: "userkey123")
api.order_list(simple_filters: [{key: "status", value: "processing"}, {key: created_at, value: "12/10/2013 12:00" }])
And to use a complex filter, pass a hash with key, operator, and value:
api.order_list(complex_filters: [{key: "status", operator: "eq", value: ["processing", "completed"]}, {key: created_at, operator: "from", value: "12/10/2013" }])
This returns an array of hashes with all your Magento orders.
Specifically, check out the request code: https://github.com/harrisjb/magento_api_wrapper/blob/master/lib/magento_api_wrapper/requests/sales_order_list.rb
While it will be easier to just use the gem, here's how I'm formatting the request prior to passing it to the SavonClient, which finishes the formatting for Magento's SOAP API:
def body
merge_filters!(sales_order_list_hash)
end
def attributes
{ session_id: { "xsi:type" => "xsd:string" },
filters: { "xsi:type" => "ns1:filters" },
}
end
def sales_order_list_hash
{
session_id: self.session_id
}
end
def merge_filters!(sales_order_list_hash)
if !filters_array.empty?
sales_order_list_filters = {
filters: filters_array,
}
sales_order_list_hash.merge!(sales_order_list_filters)
else
sales_order_list_hash
end
end
def filters_array
custom_filters = {}
custom_filters.compare_by_identity
if !simple_filters.nil?
add_simple_filters(custom_filters)
end
if !complex_filters.nil?
add_complex_filters(custom_filters)
end
custom_filters
end
def add_simple_filters(custom_filters)
simple_filters.each do |sfilter|
custom_filters[:attributes!] = {
"filter" => {
"SOAP-ENC:arrayType" => "ns1:associativeEntity[2]",
"xsi:type" => "ns1:associativeArray"
}
}
custom_filters["filter"] = {
item: {
key: sfilter[:key],
value: sfilter[:value], #formatted_timestamp(created_at)
:attributes! => {
key: { "xsi:type" => "xsd:string" },
value: { "xsi:type" => "xsd:string" }
},
},
:attributes! => {
item: { "xsi:type" => "ns1:associativeEntity" },
},
}
end
custom_filters
end
def add_complex_filters(custom_filters)
complex_filters.each do |cfilter|
custom_filters[:attributes!] = {
"complex_filter" => {
"SOAP-ENC:arrayType" => "ns1:complexFilter[2]",
"xsi:type" => "ns1:complexFilterArray"
}
}
custom_filters["complex_filter"] = {
item: {
key: cfilter[:key],
value: {
key: cfilter[:operator],
value: cfilter[:value]
},
:attributes! => {
key: { "xsi:type" => "xsd:string" },
value: { "xsi:type" => "xsd:associativeEntity" }
},
},
:attributes! => {
item: { "xsi:type" => "ns1:complexFilter" },
},
}
end
custom_filters
end
def formatted_timestamp(timestamp)
begin
Time.parse(timestamp).strftime("%Y-%m-%d %H:%M:%S")
rescue MagentoApiWrapper::BadRequest => e
raise "Did you pass date in format YYYY-MM-DD? Error: #{e}"
end
end
def status_array
data[:status_array]
end
def created_at_from
data[:created_at_from]
end
def created_at_to
data[:created_at_to]
end
def last_modified
data[:last_modified]
end
def session_id
data[:session_id]
end
def simple_filters
data[:simple_filters]
end
def complex_filters
data[:complex_filters]
end
I've also got a SavonClient that does some of the configuration for the specific API, here's most of that:
def call
client.call(#request.call_name, message: message_with_attributes, response_parser: :nokogiri)
end
#message_with_attributes are required for some specific formatting when updating Magento via the SOAP API
def message_with_attributes
#request.body.merge!(:attributes! => #request.attributes) unless #request.attributes.empty?
puts "REQUEST: #{#request.inspect}"
return #request.body
end
#configuration of the client is mostly mandatory, however some of these options (like timeout) will be made configurable in the future
#TODO: make timeout configurable
def client
Savon::Client.new do |savon|
savon.ssl_verify_mode :none
savon.wsdl base_url
savon.namespaces namespaces
savon.env_namespace 'SOAP-ENV'
savon.raise_errors false
#savon.namespace_identifier #none
savon.convert_request_keys_to :lower_camelcase
savon.strip_namespaces true
savon.pretty_print_xml true
savon.log log_env
savon.open_timeout 10 #seconds
savon.read_timeout 45 #seconds
end
end
#TODO: make configurable
def log_env
true
end
#correctly format MagentoApiWrapper::Request call_names for SOAP v2
def response_tag_format_lambda
lambda { |key| key.snakecase.downcase }
end
def namespaces
{
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:ns1' => 'urn:Magento',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/'
}
end
#Use MagentoApiWrapper::Api magento_url as endpoint
def base_url
"#{#magento_url}/api/v2_soap?wsdl=1"
end
end
Like I said, it's a work in progress, but I should have pretty good coverage of the Magento API complete in the next couple of weeks. Hope this helps you out! Good luck!

Resources