I'm getting the following error when I'm trying to query another table in a model definition.
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "miq_user_roles" LINE 1: ..." = $1 AND "service_templates"."display" = $2 AND "miq_user_... ^ [catalog/explorer]
Any idea how to resolve this?
scope :with_service_template_catalog_id, ->(cat_id) { where(:service_template_catalog_id => cat_id) }
scope :without_service_template_catalog_id, -> { where(:service_template_catalog_id => nil) }
scope :with_existent_service_template_catalog_id, -> { where.not(:service_template_catalog_id => nil) }
scope :displayed, -> { where(:display => true) }
scope :public_service_templates, -> { where("miq_user_roles.settings" => nil) }
Here is the full code:
class ServiceTemplate < ApplicationRecord
include SupportsFeatureMixin
DEFAULT_PROCESS_DELAY_BETWEEN_GROUPS = 120
GENERIC_ITEM_SUBTYPES = {
"custom" => N_("Custom"),
"vm" => N_("Virtual Machine"),
"playbook" => N_("Playbook"),
"hosted_database" => N_("Hosted Database"),
"load_balancer" => N_("Load Balancer"),
"storage" => N_("Storage")
}.freeze
SERVICE_TYPE_ATOMIC = 'atomic'.freeze
SERVICE_TYPE_COMPOSITE = 'composite'.freeze
RESOURCE_ACTION_UPDATE_ATTRS = [:dialog,
:dialog_id,
:fqname,
:configuration_template,
:configuration_template_id,
:configuration_template_type].freeze
include CustomActionsMixin
include ServiceMixin
include OwnershipMixin
include NewWithTypeStiMixin
include TenancyMixin
include ArchivedMixin
include CiFeatureMixin
include_concern 'Filter'
include_concern 'Copy'
validates :name, :presence => true
belongs_to :tenant
has_many :service_templates, :through => :service_resources, :source => :resource, :source_type => 'ServiceTemplate'
has_many :services
has_many :service_template_tenants, :dependent => :destroy
has_many :additional_tenants, :through => :service_template_tenants, :source => :tenant, :dependent => :destroy
has_one :picture, :dependent => :destroy, :as => :resource, :autosave => true
belongs_to :service_template_catalog
belongs_to :zone
belongs_to :currency, :inverse_of => false
has_many :dialogs, -> { distinct }, :through => :resource_actions
has_many :miq_schedules, :as => :resource, :dependent => :destroy
has_many :miq_requests, :as => :source, :dependent => :nullify
has_many :active_requests, -> { where(:request_state => MiqRequest::ACTIVE_STATES) }, :as => :source, :class_name => "MiqRequest"
virtual_column :type_display, :type => :string
virtual_column :template_valid, :type => :boolean
virtual_column :template_valid_error_message, :type => :string
virtual_column :archived, :type => :boolean
virtual_column :active, :type => :boolean
default_value_for :internal, false
default_value_for :service_type, SERVICE_TYPE_ATOMIC
default_value_for(:generic_subtype) { |st| 'custom' if st.prov_type == 'generic' }
virtual_has_one :config_info, :class_name => "Hash"
scope :with_service_template_catalog_id, ->(cat_id) { where(:service_template_catalog_id => cat_id) }
scope :without_service_template_catalog_id, -> { where(:service_template_catalog_id => nil) }
scope :with_existent_service_template_catalog_id, -> { where.not(:service_template_catalog_id => nil) }
scope :displayed, -> { where(:display => true) }
scope :public_service_templates, -> { where("miq_user_roles.settings" => nil) }
supports :order do
unsupported_reason_add(:order, 'Service template does not belong to a service catalog') unless service_template_catalog
unsupported_reason_add(:order, 'Service template is not configured to be displayed') unless display
end
alias orderable? supports_order?
alias validate_order supports_order?
def self.with_tenant(tenant_id)
tenant = Tenant.find(tenant_id)
where(:tenant_id => tenant.ancestor_ids + [tenant_id])
end
def self.with_additional_tenants
references(table_name, :tenants).includes(:service_template_tenants => :tenant)
end
def self.all_catalog_item_types
#all_catalog_item_types ||= begin
builtin_catalog_item_types = {
"generic" => N_("Generic"),
"generic_orchestration" => N_("Orchestration"),
}
ExtManagementSystem.subclasses_supporting(:catalog)
.flat_map(&:catalog_types)
.reduce(builtin_catalog_item_types, :merge)
end
end
def self.catalog_item_types
ems_classes = Rbac.filtered(ExtManagementSystem.all).collect(&:class).uniq.select { |ems| ems.supports?(:catalog) }
ci_types = Set.new(ems_classes.flat_map(&:catalog_types).reduce({}, :merge).keys)
ci_types.add('generic_orchestration') if Rbac.filtered(OrchestrationTemplate).exists?
ci_types.add('generic')
all_catalog_item_types.each.with_object({}) do |(key, description), hash|
hash[key] = {:description => description, :display => ci_types.include?(key)}
end
end
def self.create_catalog_item(options, auth_user)
transaction do
create_from_options(options).tap do |service_template|
config_info = options[:config_info].except(:provision, :retirement, :reconfigure)
workflow_class = MiqProvisionWorkflow.class_for_source(config_info[:src_vm_id])
if workflow_class
request = workflow_class.new(config_info, auth_user).make_request(nil, config_info)
service_template.add_resource(request)
end
service_template.create_resource_actions(options[:config_info])
end
end
end
def self.class_from_request_data(data)
request_type = data['prov_type']
if request_type.include?('generic_')
generic_type = request_type.split('generic_').last
"ServiceTemplate#{generic_type.camelize}".constantize
else
ServiceTemplate
end
end
def update_catalog_item(options, auth_user = nil)
config_info = validate_update_config_info(options)
unless config_info
update!(options)
return reload
end
transaction do
update_from_options(options)
update_service_resources(config_info, auth_user)
update_resource_actions(config_info)
save!
end
reload
end
def children
service_templates
end
def descendants
children.flat_map { |child| [child] + child.descendants }
end
def subtree
[self] + descendants
end
def vms_and_templates
[]
end
def destroy
if parent_services.present?
raise MiqException::MiqServiceError, _("Cannot delete a service that is the child of another service.")
end
service_resources.each do |sr|
rsc = sr.resource
rsc.destroy if rsc.kind_of?(MiqProvisionRequestTemplate)
end
super
end
def archive
raise _("Cannot archive while in use") unless active_requests.empty?
archive!
end
def retireable?
false
end
def request_class
ServiceTemplateProvisionRequest
end
def request_type
"clone_to_service"
end
def config_info
options[:config_info] || construct_config_info
end
def create_service(service_task, parent_svc = nil)
nh = attributes.dup
# Service#display was renamed to #visible in https://github.com/ManageIQ/manageiq-schema/pull/410
nh['visible'] = nh.delete('display') if nh.key?('display')
nh['options'][:dialog] = service_task.options[:dialog]
(nh.keys - Service.column_names + %w(created_at guid service_template_id updated_at id type prov_type)).each { |key| nh.delete(key) }
# Hide child services by default
nh['visible'] = false if parent_svc
# If visible is nil, set it to false
nh['visible'] ||= false
# convert template class name to service class name by naming convention
nh['type'] = self.class.name.sub('Template', '')
nh['initiator'] = service_task.options[:initiator] if service_task.options[:initiator]
service = Service.create!(nh) do |svc|
svc.service_template = self
set_ownership(svc, service_task.get_user)
service_resources.each do |sr|
nh = sr.attributes.dup
%w(id created_at updated_at service_template_id).each { |key| nh.delete(key) }
svc.add_resource(sr.resource, nh) unless sr.resource.nil?
end
end
service.tap do |svc|
if parent_svc
service_resource = ServiceResource.find_by(:id => service_task.options[:service_resource_id])
parent_svc.add_resource!(svc, service_resource)
end
end
end
def composite?
service_type.to_s.include?(self.class::SERVICE_TYPE_COMPOSITE)
end
def atomic?
service_type.to_s.include?(self.class::SERVICE_TYPE_ATOMIC)
end
def type_display
case service_type
when self.class::SERVICE_TYPE_ATOMIC then "Item"
when self.class::SERVICE_TYPE_COMPOSITE then "Bundle"
when nil then "Unknown"
else
service_type.to_s.capitalize
end
end
def create_tasks_for_service(service_task, parent_svc)
unless parent_svc
return [] unless self.class.include_service_template?(service_task,
service_task.source_id,
parent_svc)
end
svc = create_service(service_task, parent_svc)
service_task.destination = svc
create_subtasks(service_task, svc)
end
# default implementation to create subtasks from service resources
def create_subtasks(parent_service_task, parent_service)
tasks = []
service_resources.each do |child_svc_rsc|
scaling_min = child_svc_rsc.scaling_min
1.upto(scaling_min).each do |scaling_idx|
nh = parent_service_task.attributes.dup
%w(id created_on updated_on type state status message).each { |key| nh.delete(key) }
nh['options'] = parent_service_task.options.dup
nh['options'].delete(:child_tasks)
# Initial Options[:dialog] to an empty hash so we do not pass down dialog values to child services tasks
nh['options'][:dialog] = {}
next if child_svc_rsc.resource_type == "ServiceTemplate" &&
!self.class.include_service_template?(parent_service_task,
child_svc_rsc.resource.id,
parent_service)
new_task = parent_service_task.class.new(nh)
new_task.options.merge!(
:src_id => child_svc_rsc.resource.id,
:scaling_idx => scaling_idx,
:scaling_min => scaling_min,
:service_resource_id => child_svc_rsc.id,
:parent_service_id => parent_service.id,
:parent_task_id => parent_service_task.id,
)
new_task.state = 'pending'
new_task.status = 'Ok'
new_task.source = child_svc_rsc.resource
new_task.save!
new_task.after_request_task_create
parent_service_task.miq_request.miq_request_tasks << new_task
tasks << new_task
end
end
tasks
end
def set_ownership(service, user)
return if user.nil?
service.evm_owner = user
if user.current_group
$log.info("Setting Service Owning User to Name=#{user.name}, ID=#{user.id}, Group to Name=#{user.current_group.name}, ID=#{user.current_group.id}")
service.miq_group = user.current_group
else
$log.info("Setting Service Owning User to Name=#{user.name}, ID=#{user.id}")
end
end
def self.default_provisioning_entry_point(service_type)
if service_type == 'atomic'
'/Service/Provisioning/StateMachines/ServiceProvision_Template/CatalogItemInitialization'
else
'/Service/Provisioning/StateMachines/ServiceProvision_Template/CatalogBundleInitialization'
end
end
def self.default_retirement_entry_point
'/Service/Retirement/StateMachines/ServiceRetirement/Default'
end
def self.default_reconfiguration_entry_point
nil
end
def template_valid?
validate_template[:valid]
end
alias template_valid template_valid?
def template_valid_error_message
validate_template[:message]
end
def validate_template
missing_resources = service_resources.select { |sr| sr.resource.nil? }
if missing_resources.present?
missing_list = missing_resources.collect { |sr| "#{sr.resource_type}:#{sr.resource_id}" }.join(", ")
return {:valid => false,
:message => "Missing Service Resource(s): #{missing_list}"}
end
service_resources.detect do |s|
r = s.resource
r.respond_to?(:template_valid?) && !r.template_valid?
end.try(:resource).try(:validate_template) || {:valid => true, :message => nil}
end
def provision_action
resource_actions.find_by(:action => "Provision")
end
def update_resource_actions(ae_endpoints)
resource_action_list.each do |action|
resource_params = ae_endpoints[action[:param_key]]
resource_action = resource_actions.find_by(:action => action[:name])
# If the action exists in updated parameters
if resource_params
# And the resource action exists on the template already, update it
if resource_action
resource_action.update!(resource_params.slice(*RESOURCE_ACTION_UPDATE_ATTRS))
# If the resource action does not exist, create it
else
build_resource_action(resource_params, action)
end
elsif resource_action
# If the endpoint does not exist in updated parameters, but exists on the template, delete it
resource_action.destroy
end
end
end
def create_resource_actions(ae_endpoints)
ae_endpoints ||= {}
resource_action_list.each do |action|
ae_endpoint = ae_endpoints[action[:param_key]]
next unless ae_endpoint
build_resource_action(ae_endpoint, action)
end
save!
end
def self.create_from_options(options)
create!(options.except(:config_info).merge(:options => { :config_info => options[:config_info] }))
end
private_class_method :create_from_options
def provision_request(user, options = nil, request_options = {})
request_options[:provision_workflow] = true
request_options[:parent_id] = options.delete('param_parent_request_id') unless options['param_parent_request_id'].nil?
result = order(user, options, request_options)
raise result[:errors].join(", ") if result[:errors].any?
result[:request]
end
def picture=(value)
if value.kind_of?(Hash)
super(Picture.new(value))
else
super
end
end
def queue_order(user_id, options, request_options)
MiqQueue.submit_job(
:class_name => self.class.name,
:instance_id => id,
:method_name => "order",
:args => [user_id, options, request_options],
)
end
def order(user_or_id, options = nil, request_options = {}, schedule_time = nil)
user = user_or_id.kind_of?(User) ? user_or_id : User.find(user_or_id)
workflow = provision_workflow(user, options, request_options)
if schedule_time
require 'time'
time = Time.parse(schedule_time).utc
errors = workflow.validate_dialog
errors << unsupported_reason(:order)
return {:errors => errors} if errors.compact.present?
schedule = MiqSchedule.create!(
:name => "Order #{self.class.name} #{id} at #{time}",
:description => "Order #{self.class.name} #{id} at #{time}",
:sched_action => {:args => [user.id, options, request_options], :method => "queue_order"},
:resource => self,
:run_at => {
:interval => {:unit => "once"},
:start_time => time,
:tz => "UTC",
},
)
{:schedule => schedule}
else
workflow.submit_request
end
end
def provision_workflow(user, dialog_options = nil, request_options = {})
dialog_options ||= {}
request_options.delete(:provision_workflow) if request_options[:submit_workflow]
ra_options = request_options.slice(:initiator, :init_defaults, :provision_workflow, :submit_workflow).merge(:target => self)
ResourceActionWorkflow.new(dialog_options, user, provision_action, ra_options).tap do |wf|
wf.request_options = request_options
end
end
def add_resource(rsc, options = {})
super
adjust_service_type
end
def self.display_name(number = 1)
n_('Service Catalog Item', 'Service Catalog Items', number)
end
def my_zone
# Catalog items can specify a zone to run in.
# Catalog bundle are used for grouping catalog items and are therefore treated as zone-agnostic.
zone&.name if atomic?
end
private
def update_service_resources(config_info, auth_user = nil)
config_info = config_info.except(:provision, :retirement, :reconfigure)
workflow_class = MiqProvisionWorkflow.class_for_source(config_info[:src_vm_id])
if workflow_class
service_resources.find_by(:resource_type => 'MiqRequest').try(:destroy)
new_request = workflow_class.new(config_info, auth_user).make_request(nil, config_info)
add_resource!(new_request)
end
end
def build_resource_action(ae_endpoint, action)
fqname = ae_endpoint[:fqname] || self.class.send(action[:method], *action[:args]) || ""
build_options = {:action => action[:name],
:fqname => fqname,
:ae_attributes => {:service_action => action[:name]}}
build_options.merge!(ae_endpoint.slice(*RESOURCE_ACTION_UPDATE_ATTRS))
resource_actions.build(build_options)
end
def validate_update_config_info(options)
if options[:service_type] && options[:service_type] != service_type
raise _('service_type cannot be changed')
end
if options[:prov_type] && options[:prov_type] != prov_type
raise _('prov_type cannot be changed')
end
options[:config_info]
end
def resource_action_list
[
{:name => ResourceAction::PROVISION,
:param_key => :provision,
:method => 'default_provisioning_entry_point',
:args => [service_type]},
{:name => ResourceAction::RECONFIGURE,
:param_key => :reconfigure,
:method => 'default_reconfiguration_entry_point',
:args => []},
{:name => ResourceAction::RETIREMENT,
:param_key => :retirement,
:method => 'default_retirement_entry_point',
:args => []}
]
end
def update_from_options(params)
options[:config_info] = params[:config_info]
update!(params.except(:config_info))
end
def construct_config_info
config_info = {}
miq_request_resource = service_resources.find_by(:resource_type => 'MiqRequest')
config_info.merge!(miq_request_resource.resource.options.compact) if miq_request_resource
config_info.merge!(resource_actions_info)
end
def resource_actions_info
resource_actions.each_with_object({}) do |resource_action, config_info|
resource_options = resource_action.slice(:dialog_id, :configuration_template_type, :configuration_template_id).compact
resource_options[:fqname] = resource_action.fqname
config_info[resource_action.action.downcase.to_sym] = resource_options.symbolize_keys
end
end
def generic_custom_buttons
CustomButton.buttons_for("Service")
end
def adjust_service_type
self.service_type = service_resources.any? { |st| st.resource_type.in?(['Service', 'ServiceTemplate']) } ? self.class::SERVICE_TYPE_COMPOSITE : self.class::SERVICE_TYPE_ATOMIC
end
end
scope :public_service_templates, -> {
joins(:miq_user_roles).where(miq_user_roles: { settings: nil })
}
Assuming the table exists. You need to join on that table in order to query it. Note, the default joins is an inner join that will remove records that don't have an associated miq_user_roles.
This will change the query substantially. If a record has_many miq_user_roles you'll likely need to add a distinct or distinct on clause. If it's possible to have no miq_user_roles, then records without them will now dissappear when you call the scope, since the inner join didn't find any.
I am using recycled code from a project but in this version I am not having good results.
I use Rails 5.2.2 and RVM Ruby 2.7.1
I need to use this function to call an ajax and deliver the already stored data of a client and fill out a form, the data will be searched through the RUN of each client
I don't understand why the match () are not working for me
Controller Pacientes
class Ajax::PacientesController < ApplicationController
layout nil
def obtener_datos_paciente
#usuario = params[:rut]
usuario = Usuario.first :rut => params[:rut]
puts usuario.inspect.yellow
if usuario.nil?
render :json => {
:exito => true,
:mensaje => "No existen registros asociados al rut #{params[:rut]}."
}
else
render :json => {
:exito => true,
:es_empresa => true,
:mensaje => "El paciente con rut #{params[:rut]} ya existe.",
:data => {
:id => usuario.id,
:rut => usuario.rut,
:primer_nombre => usuario.primer_nombre,
:segundo_nombre => usuario.segundo_nombre,
:apellido_paterno => usuario.apellido_paterno,
:apellido_materno => usuario.apellido_materno,
:direccion => usuario.direccion,
:ciudad => usuario.ciudad,
:comuna => usuario.comuna,
:telefono => usuario.telefono,
:email => usuario.email
}
}
end
rescue Excepciones::DatosNoExistentesError => e
flash.now[:info] = e.message
render :json => { :mensaje => e.message }
end
end
Routes
match(
"ajax/pacientes/:rut" => "ajax::pacientes#obtener_datos_paciente",
:as => :obtener_datos_paciente,
:via => :get
)
Controller Usuario
require 'json'
class UsuariosController < ApplicationController
helper_method :url_paciente
def index
#usuarios = Usuario.all
end
def ingreso_paciente
end
def registrar_ingreso
end
def ingresar_ficha_kinesica
alias url_paciente obtener_datos_paciente_ajax_pacientes_path
end
end
The easiest fix would be to rename your controller to:
class PacientesController < ApplicationController and match to "ajax/pacientes/:rut" => "pacientes#obtener_datos_paciente"
If your controller must exist in the Ajax namespace, then it should probably have a namespaced route as well. An example can be found in this answer.
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
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!
I am fairly still new to ruby on rails and don't fully understand why I am getting the following error:
undefined local variable or method `user' for #<StatisticsController:0xb9a20d0>
The code:
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
Project.all.each do |project|
projdata = { 'name' => project.project_name.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values' ].push( record )
end
#data.push( projdata )
end
end
end
Update
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
user = User.all
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
end
end
In string :conditions => { "project_tasks.efforts.user_id" => user.id, you call id for user object, but it is not instantiated in code above.
Your update doesn't loop over the users at all; user is now a collection of all the users. You need to iterate over the users if you want to get individual statistics for individual users.
Are you using devise? Use current_user instead of user.
Fix of your code:
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
So: removed the rogue user=User.all :)
Question: in 1 place you write user.user_id and in the other you write user.id. Is that correct?