rails datatables error sort descending - ruby-on-rails

I'm new to Rails. Running Rails 4, and gem ajax-datatables-rails.
I have a few instances of datatables that are working without issue, but one table has an error when I try to sort any column descending. I can click on the header of any column and sort ascending. But if I click on that header again I get the following error:
DataTables warning: table id=DataTables_Table_0 - Ajax error. For more information about this error, please see http://datatables.net/tn/7
Log shows this after getting the error:
Completed 500 Internal Server Error in 14ms (ActiveRecord: 2.1ms)
NoMethodError (undefined method `name' for nil:NilClass):
app/datatables/animal_registration_datatable.rb:29:in `block in data'
app/datatables/animal_registration_datatable.rb:25:in `data'
app/controllers/animal_registrations_controller.rb:8:in `block (2 levels) in index'
app/controllers/animal_registrations_controller.rb:5:in `index'
animal_registration_datatable.rb
class AnimalRegistrationDatatable < AjaxDatatablesRails::Base
include AjaxDatatablesRails::Extensions::Kaminari
def sortable_columns
# Declare strings in this format: ModelName.column_name
if breeder?
#sortable_columns ||= %w(AnimalRegistration.created_at Animal.name Animal.tag_number AnimalRegistration.status)
else
#sortable_columns ||= %w(AnimalRegistration.created_at Animal.name Contact.last_name Contact.farm_name)
end
end
def searchable_columns
# Declare strings in this format: ModelName.column_name
if breeder?
#searchable_columns ||= %w(Animal.name Animal.tag_number AnimalRegistration.status)
else
#searchable_columns ||= %w(AnimalRegistration.farm_name Animal.name Contact.last_name Contact.first_name AnimalRegistration.created_at)
end
end
private
def data
records.map do |record|
if breeder?
{
'0' => record.created_at.strftime('%x - ''%l:%m %p'),
'1' => record.animal.name,
'2' => record.animal.tag_number,
'3' => record.status,
'DT_RowId' => record.id
}
else
{
'0' => record.created_at.strftime('%x - ''%l:%m %p'),
'1' => record.animal.try(:name),
'2' => record.breeder_contact_id ? record.breeder_contact.last_name + ', ' + record.breeder_contact.first_name : "",
'3' => record.breeder_contact_id ? record.breeder_contact.farm_name : "",
'4' => record.datatable_row_controls,
'DT_RowId' => record.id
}
end
end
end
def user
#user ||= options[:user] #if #user is null, set it to options[:user]
end
def breeder?
! user.organization
end
def status
#status ||= options[:status]
#status ||= "pending"
end
def get_raw_records
if breeder?
AnimalRegistration.where(user_id: user.id).includes(:animal).references(:animal).distinct
else
AnimalRegistration.where(organization_id: user.organization.id, status: status).includes(:animal, :breeder_contact).references(:animal, :breeder_contact).distinct
end
end
# ==== Insert 'presenter'-like methods below if necessary
end
Note that I am logged in as a breeder (if breeder? = true). Note also that when logged in as org (if breeder? = false), this error does not occur.
From animal_registrations_controller.rb:
def index
respond_to do |format|
format.html
format.json do
render json: AnimalRegistrationDatatable.new(view_context, user: current_user, status: params[:status])
end
end
end
animal_registrations.js.coffee:
$ ->
dt = $("table.animal-registrations.ajax-table")
if dt.hasClass("org-admin")
dtCols = [
{ data: '0' }
{ data: '1' }
{ data: '2' }
{ data: '3' }
{ data: '4', orderable: false }
]
else
dtCols = [
{ data: '0' }
{ data: '1' }
{ data: '2' }
{ data: '3' }
]
$("table.animal-registrations.ajax-table").DataTable(
processing: true
serverSide: true
ajax: $('table.animals').data('source')
pagingType: 'full'
pageLength: 20
lengthChange: false
language:
search: "<i class='fa fa-search'/>"
sFilterInput: "form-control input-lg"
# drawCallback: (settings) ->
# bindAnimalRowClickDatatable()
columns: dtCols
)
Note that if dt.hasClass("org-admin") is false.
Lots of moving parts here. What I can't figure out is why it would work when sorted ascending but not descending.
Thanks for any help.

Related

i have code which implement upload song to database using plupload-jquery and show in datatable for management but song not saved to database

uploaded_song_datatable.rb --which is used for showing list of song in datatable.
class UploadedSongDatatable < AjaxDatatablesRails::Base
include AjaxDatatablesRails::Extensions::Kaminari
def_delegator :#view, :best_in_place
def_delegator :#view, :link_to
def_delegator :#view, :uploaded_song_path
def_delegator :#view, :autocomplete_album_name_albums_path
def_delegator :#view, :autocomplete_artist_name_artists_path
def_delegator :#view, :autocomplete_genre_name_genres_path
def_delegator :#view, :year_list
def_delegator :#view, :content_tag
def_delegator :#view, :image_tag
def sortable_columns
#sortable_columns ||= [
'UploadedSong.title',
'UploadedSong.artist',
'UploadedSong.album',
'UploadedSong.genre'
]
end
def searchable_columns
#searchable_columns ||= [
'UploadedSong.title',
'UploadedSong.artist',
'UploadedSong.album',
'UploadedSong.genre'
]
end
private
def data
records.map do |record|
[
# best_in_place(record, , :as => :checkbox, :collection => ['<input name="mark[]" type="checkbox">'.html_safe, '<input type="checkbox" checked="true">'.html_safe]),
nil,
"<input type='checkbox' class='checkBoxClass' name=\"songids[]\" value='#{record.id}' >",
best_in_place(record, :is_explicit, :as => :checkbox, :collection => ['<input type="checkbox">'.html_safe, '<input type="checkbox" checked="true">'.html_safe] , url: uploaded_song_path(record, field_to_update: 'is_explicit')),
best_in_place(record, :title, :display_with => lambda { |v| v.blank? ? "( No title )" : v }, url: uploaded_song_path(record, field_to_update: 'title')),
best_in_place(record, :artist, :display_with => lambda { |v| v.blank? ? "( No artist )" : v }, url: uploaded_song_path(record, field_to_update: 'artist'), html_attrs: { "data-autocomplete" => autocomplete_artist_name_artists_path, "object_id" => record.id.to_s }),
best_in_place(record, :album, :display_with => lambda { |v| v.blank? ? "( No album )" : v }, url: uploaded_song_path(record, field_to_update: 'album'), html_attrs: { "data-autocomplete" => autocomplete_album_name_albums_path }),
best_in_place(record, :genre, :display_with => lambda { |v| v.blank? ? "( No genre )" : v }, url: uploaded_song_path(record, field_to_update: 'genre'), html_attrs: { "data-autocomplete" => autocomplete_genre_name_genres_path }),
best_in_place(record, :year, :as => :select, :collection => year_list, :display_with => lambda { |v| v.blank? ? "( No year )" : v }, url: uploaded_song_path(record, field_to_update: 'year')),
best_in_place(record, :track, :display_with => lambda { |v| v.blank? ? "( No track )" : v }, url: uploaded_song_path(record, field_to_update: 'track')),
best_in_place(record, :comment, :display_with => lambda { |v| v.blank? ? "( No comment )" : v }, url: uploaded_song_path(record, field_to_update: 'comment')),
if record.cover_id.blank?
link_to("No Cover", "#cover_modal", data: {song_id: record.id, toggle: "modal", multiqueue: 'false'})
else
link_to("#") do
image_tag(record.cover.cover_pic.url, href: "#cover_modal", height: '50', width: '50', data: {song_id: record.id, toggle: "modal", src: record.cover.cover_pic.url, multiqueue: 'false'})
end
end,
link_to(uploaded_song_path(record), method: :delete, data: { confirm: "Are you sure to delete the song ?"}) do
content_tag :span, '', class: "glyphicon glyphicon-trash glyphicon-red"
end
]
end
end
def get_raw_records
UploadedSong.all
end
end
uploaded_song_controller.rb
require 'taglib'
class UploadedSongsController < ApplicationController
before_action :authenticate_user!
authorize_resource
respond_to :json, :html, :js
def index
if params[:from_modal]
sleep(10)
flash[:notice] = "Click 'Refresh', to view recently uploaded songs."
end
#songs = UploadedSong.count
respond_to do |format|
format.html
format.json { render json: UploadedSongDatatable.new(view_context) }
end
end
def new
#song = UploadedSong.new
end
def create
Rails.logger.debug params[:file]
extraction_path = Pathname.new("#{Rails.root}/public")
file_ext = File.extname(params[:file].original_filename)
original_file_name = params[:file].original_filename
tmp_file_base_name = File.basename(params[:file].path)
uploaded_tmp_file = "#{extraction_path}/#{tmp_file_base_name}"
uploaded_file = "#{extraction_path}/#{original_file_name}"
if params[:file].content_type == "application/zip"
Rails.logger.debug "moving #{params[:file].path} to #{extraction_path}"
FileUtils.mv(params[:file].path, extraction_path)
Rails.logger.debug "file moved"
if File.rename(uploaded_tmp_file, uploaded_file)
Resque.enqueue(ZipExtractor, uploaded_file, extraction_path.to_s, true, false)
Rails.logger.debug "Background job started"
head 200
else
File.delete(uploaded_tmp_file) if File.exist?(uploaded_tmp_file)
end
elsif %w(.mp3 .ogg .mp4 .m4a).include?(file_ext)
FileUtils.mv(params[:file].path, extraction_path)
if File.rename(uploaded_tmp_file, uploaded_file)
Rails.logger.info "renaming #{tmp_file_base_name} to #{original_file_name}"
Resque.enqueue(ZipExtractor, false, uploaded_file, false, false)
head 200
else
File.delete(uploaded_tmp_file) if File.exist?(uploaded_tmp_file)
head 200
end
else
File.delete(params[:file].path) if File.exist?(params[:file].path)
head 200
end
end
def edit
#song = UploadedSong.find(params[:id])
end
def update
#song = UploadedSong.find(params[:id])
#song.field_to_update = (params[:field_to_update])
if #song.update_attributes(uploaded_song_params)
Resque.enqueue(ZipExtractor, #song.id, false, false, #song.field_to_update)
end
respond_with_bip #song
end
def destroy
#song = UploadedSong.find(params[:id])
#song.destroy!
unless #song.cover_id.blank?
cover = Cover.find(#song.cover_id)
cover.destroy!
end
if session[:ids] == nil
respond_to do |format|
format.html { redirect_to uploaded_songs_url, notice: 'Song was successfully destroyed.' }
format.json { head :no_content }
end
else
session[:ids].delete(#song.id)
respond_to do |format|
format.html { redirect_to uploaded_songs_url, notice: 'Song was successfully destroyed.' }
format.json { head :no_content }
end
end
end
private
def uploaded_song_params
params.require(:uploaded_song).permit(:title, :artist, :album, :year, :track, :genre, :comment, :song_name, :attachment, :is_explicit)
end
def set_session_for_ids
if session[:ids] == nil
session[:ids] = Array.new
end
end
def update_music_metadata?(song)
TagLib::FileRef.open(song.attachment_url) do |fileref|
unless fileref.null?
tag = fileref.tag # true is for writing.
tag.title = song.title
tag.artist = song.artist
tag.album = song.album
tag.year = song.year.to_i
tag.track = song.track.to_i
tag.genre = song.genre
tag.comment = song.comment
if fileref.save
return true
else
return false
end
end
end
end
def update_song_name?(song)
file_handle = File.open(song.song_path)
dir_location = File.dirname(song.song_path)
file_ext = File.extname(song.song_path)
song_name = song.song_name + file_ext
song_path = dir_location + "/#{song_name}"
attachment = File.rename_file(file_handle, song_path)
if song.update_attributes({attachment: attachment, song_name: song_name, song_path: song_path})
return true
else
return false
end
end
end
uploaded_song.rb
require 'file_size_validator'
class UploadedSong < ActiveRecord::Base
belongs_to :cover
mount_uploader :attachment, AttachmentUploader
skip_callback :commit, :after, :remove_attachment!, if: Proc.new{ |s| s.file_keep == true }
validates :attachment, :file_size => { less_than: 200.megabytes }
attr_accessor :file_keep, :field_to_update
end
zip_extractor.rb
require 'taglib'
require 'zip'
class ZipExtractor
#queue = :songs_queue
def self.perform path_to_zip, destination_path, delete = false, field_to_update
if !path_to_zip.blank? and delete and !destination_path.blank? and !field_to_update
unzip(path_to_zip, destination_path)
Dir.glob("#{destination_path}" + '/*') do |music_file|
base_file_name = File.basename(music_file)
next if base_file_name.casecmp("__macosx") == 0
process_song(music_file)
end
end
if !path_to_zip and !delete and !destination_path.blank? and !field_to_update
process_song(destination_path)
end
if !path_to_zip.blank? and !delete and !destination_path and !field_to_update.blank?
log("processing song details update")
song = UploadedSong.find(path_to_zip)
song_details = nil
TagLib::FileRef.open(song.attachment_url) do |fileref|
unless fileref.null?
tag = fileref.tag
case field_to_update.to_s
when "title"
tag.title = song.title
when "artist"
tag.artist = song.artist
when "album"
tag.album = song.album
when "year"
tag.year = song.year.to_i
when "track"
tag.track = song.track.to_i
when "genre"
tag.genre = song.genre
when "comment"
tag.comment = song.comment
else
log 'No parameters for "field_to_update".'
end
unless fileref.save
TagLib::FileRef.open(song.attachment_url) do |fileref|
unless fileref.null?
tag = fileref.tag
song_details = {
title: tag.title,
artist: tag.artist,
album: tag.album,
year: tag.year,
track: tag.track,
genre: tag.genre,
comment: tag.comment,
is_explicit: false
}
end
end
if song_details[:title].downcase.include? 'explicit'
song_details[:is_explicit] = true
end
song.update_attributes(song_details)
end
end
end
end
end
def self.process_song(destination_path)
if %w(.mp3 .ogg .mp4 .m4a).include? File.extname(destination_path)
if %w(.ogg .mp4 .m4a).include? File.extname(destination_path)
destination_path = convert_video_to_audio(destination_path)
end
if %w(.mp3).include? File.extname(destination_path)
process_mp3(destination_path)
else
delete_file(destination_path)
end
else
delete_file(destination_path)
end
end
def self.process_mp3(destination_path)
song_details = extract_metadata(destination_path)
rename_file = File.dirname(destination_path) + "/1-----" + File.basename(destination_path)
extension = File.extname(destination_path)
cover_file_name = nil
rename = File.rename(destination_path, rename_file)
if rename == 0
transcoded_movie, cover_file_name = transcode_file(rename_file, destination_path)
if transcoded_movie.valid?
delete_file(rename_file)
song_details[:attachment] = File.open(destination_path) if File.exist?(destination_path)
# cover = nil
p song_details[:attachment]
unless cover_file_name.nil?
cover = Cover.new({name: File.basename(cover_file_name), cover_pic: File.open(cover_file_name)}) if File.exist?(cover_file_name)
end
song = UploadedSong.new(song_details)
if !cover.blank?
if cover.save
delete_file(cover_file_name)
song.cover_id = cover.id
if song.save
delete_file(destination_path)
end
end
else
p 9999999999999999999999999999999999999999999999999999
p song.valid?
p song.save
p song.errors.any?
p song.errors.full_messages
p 9999999999999999999999999999999999999999999999999999
if song.save!
delete_file(destination_path)
end
end
else
delete_file(destination_path)
delete_file(rename_file)
end
else
delete_file(destination_path)
end
end
def self.convert_video_to_audio(destination_path)
log "Found video file. Converting video to audio."
movie = FFMPEG::Movie.new(destination_path)
if movie.valid?
file_name_with_no_ext = File.basename(destination_path, "#{File.extname(destination_path)}")
out_file = "#{File.dirname(destination_path)}/#{file_name_with_no_ext}.mp3"
out_movie = movie.transcode(out_file)
if out_movie.valid?
delete_file(destination_path)
destination_path = out_file
end
end
return destination_path
end
def self.extract_metadata(destination_path)
log("extracting metadata from media file")
song_details = nil
TagLib::FileRef.open(destination_path) do |fileref|
unless fileref.null?
tag = fileref.tag
song_details =
{
title: tag.title.blank? ? 'Single' : tag.title,
artist: tag.artist.blank? ? 'Single' : tag.artist,
album: tag.album.blank? ? 'Single' : tag.album,
year: tag.year.blank? ? '' : tag.year,
track: tag.track.blank? ? '' : tag.track,
genre: tag.genre.blank? ? '' : tag.genre,
comment: tag.comment.blank? ? '' : tag.comment,
is_explicit: false
}
if song_details[:title].downcase.include? 'explicit'
song_details[:is_explicit] = true
end
if tag.title.blank?
tag.title = "Single"
fileref.save
elsif tag.artist.blank?
tag.artist = "Single"
fileref.save
elsif tag.album.blank?
tag.album = "Single"
fileref.save
end
end
end
return song_details
end
def self.transcode_file(rename_file, destination_path)
movie = FFMPEG::Movie.new(rename_file)
cover_file_name = nil
transcoded_movie = nil
p movie.valid?
if movie.valid?
log "ffmpeg validates file #{rename_file}"
if movie.video_stream == nil
options = {audio_codec: "libmp3lame", audio_bitrate: 320, audio_sample_rate: 44100, audio_channels: 2}
else
log "removing video stream from file and extracting cover"
cover_file_name = extract_image_from_file(rename_file)
# "-vn" flag is used to remove video_stream from mp3 file
options = {audio_codec: "libmp3lame", audio_bitrate: 320, audio_sample_rate: 44100, audio_channels: 2, custom: "-vn"}
end
transcoded_movie = movie.transcode(destination_path, options)
else
log "Unable to process media file."
delete_file(rename_file)
end
return transcoded_movie, cover_file_name
end
def self.extract_image_from_file(rename_file)
cover_file_name = nil
cover_path = "#{Rails.root}/public/covers/"
TagLib::MPEG::File.open(rename_file) do |fileref|
cover_tag = fileref.id3v2_tag
cover_img = cover_tag.frame_list('APIC').first
unless cover_img.blank?
ext = cover_img.mime_type.rpartition('/')[2]
o = [('a'..'i'), ('A'..'Z')].map { |i| i.to_a }.flatten
rand_string = (1..18).map { o[rand(o.length)] }.join
cover_file_name = "#{cover_path}#{File.basename(rename_file).chomp('.mp3').gsub('1-----','')}-#{rand_string}.#{ext}"
File.open(cover_file_name, "wb") { |f| f.write cover_img.picture }
log "cover extracted from media file."
end
end
return cover_file_name
end
def self.delete_file(filename)
File.delete(filename) if File.exist?(filename)
end
def self.log(message)
Rails.logger.debug "\n*********** #{message} ***************"
end
def self.unzip(path_to_zip, destination_path)
log "unzipping #{path_to_zip} \nto #{destination_path}"
Zip::File.open(path_to_zip) do |zip_file|
# log "zip file is #{zip_file}"
zip_file.each do |f|
f_path=File.join(destination_path, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
a = zip_file.extract(f, f_path) unless File.exist?(f_path)
# log "file extraction complete"
# log a
end
log "after zip file loop"
end
# log "removing original zip file"
FileUtils.rm(path_to_zip)
# log "removed zip file from #{path_to_zip}"
end
end
in above means zip_extractor.rb I checked song.valid? it gives me false in rails 6 and error is cover must exist
and I have checked cover is nil but in rails 4 it gives true and also checked in this version also cover is nil but song saved in database in rails but not in rails 6. anyone have proper reason why it behave like this answer fast otherwise mailto: santu.essence#gmail.com

Rails class gives NoMethodError undefined method `each'

I'm working with redmine and I've installed a plugin for managing mails.
When I try to send a mail I obtain the following error
[ActiveJob] [ActionMailer::DeliveryJob] [uuid] Error performing ActionMailer::DeliveryJob (Job ID: uuid) from Async(mailers) in 41.81ms: NoMethodError (undefined method `each' for #<User:id>):
This is the file that gives me the error
module EncryptMails
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method :mail_without_relocation, :mail
alias_method :mail, :mail_with_relocation
end
end
module InstanceMethods
# action names to be processed by this plugin
def actions
[
'attachments_added',
'document_added',
'issue_add',
'issue_edit',
'message_posted',
'news_added',
'news_comment_added',
'wiki_content_added',
'wiki_content_updated'
]
end
# dispatched mail method
def mail_with_relocation(headers={}, &block)
# pass unchanged, if action does not match or plugin is inactive
act = Setting.plugin_openpgp['activation']
return mail_without_relocation(headers, &block) if
act == 'none' or not actions.include? #_action_name or
(act == 'project' and not project.try('module_enabled?', 'openpgp'))
# relocate recipients
recipients = relocate_recipients(headers)
header = #_message.header.to_s
# render and deliver encrypted mail
reset(header)
m = mail_without_relocation prepare_headers(
headers, recipients[:encrypted], encrypt = true, sign = true
) do |format|
format.text
end
m.deliver
# render and deliver filtered mail
reset(header)
tpl = #_action_name + '.filtered'
m = mail_without_relocation prepare_headers(
headers, recipients[:filtered], encrypt = false, sign = true
) do |format|
format.text { render tpl }
format.html { render tpl } unless Setting.plain_text_mail?
end
m.deliver
# render unchanged mail (deliverd by calling method)
reset(header)
m = mail_without_relocation prepare_headers(
headers, recipients[:unchanged], encrypt = false, sign = false
) do |format|
format.text
format.html unless Setting.plain_text_mail?
end
m
end
# get project dependent on action and object
def project
case #_action_name
when 'attachments_added'
#attachments.first.project
when 'document_added'
#document.project
when 'issue_add', 'issue_edit'
#issue.project
when 'message_posted'
#message.project
when 'news_added', 'news_comment_added'
#news.project
when 'wiki_content_added', 'wiki_content_updated'
#wiki_content.project
else
nil
end
end
# relocates reciepients (to, cc) of message
def relocate_recipients(headers)
# hash to be returned
recipients = {
:encrypted => {:to => [], :cc => []},
:blocked => {:to => [], :cc => []},
:filtered => {:to => [], :cc => []},
:unchanged => {:to => [], :cc => []},
:lost => {:to => [], :cc => []}
}
# relocation of reciepients
[:to, :cc].each do |field|
headers[field].each do |user|
# encrypted
unless Pgpkey.find_by(user_id: user.id).nil?
recipients[:encrypted][field].push user and next
end
# unencrypted
case Setting.plugin_openpgp['unencrypted_mails']
when 'blocked'
recipients[:blocked][field].push user
when 'filtered'
recipients[:filtered][field].push user
when 'unchanged'
recipients[:unchanged][field].push user
else
recipients[:lost][field].push user
end
end unless headers[field].blank?
end
recipients
end
# resets the mail for sending mails multiple times
def reset(header)
#_mail_was_called = false
#_message = Mail.new
#_message.header header
end
# prepares the headers for different configurations
def prepare_headers(headers, recipients, encrypt, sign)
h = headers.deep_dup
# headers for recipients
h[:to] = recipients[:to]
h[:cc] = recipients[:cc]
# headers for gpg
h[:gpg] = {
encrypt: false,
sign: false
}
# headers for encryption
if encrypt
h[:gpg][:encrypt] = true
# add pgp keys for emails
h[:gpg][:keys] = {}
[:to, :cc].each do |field|
h[field].each do |user|
user_key = Pgpkey.find_by user_id: user.id
unless user_key.nil?
h[:gpg][:keys][user.mail] = user_key.fpr
end
end unless h[field].blank?
end
end
# headers for signature
if sign
server_key = Pgpkey.find_by(:user_id => 0)
unless server_key.nil?
h[:gpg][:sign] = true
h[:gpg][:sign_as] = Setting['mail_from']
h[:gpg][:password] = server_key.secret
end
end
h
end
end
end
The stack of the log tells me that the error is in row 109
# relocation of reciepients
[:to, :cc].each do |field|
I'm not an expert of ruby and rails but I've seen that each is a method of a Ruby array, not a custom one, so I don't understand why I obtain the error.
What I am doing wrong and how can I fix this error?
Are you sure the problem isn't on this line?
h[field].each do |user|
The field there would be :to or :cc so h[field] could be a User instance.
If you want to allow h[:to] or h[:cc] to be a single User or an array of Users, then wrap it in Array():
# relocation of reciepients
[:to, :cc].each do |field|
Array(headers[field]).each do |user|
#^^^^
I'd also move that trailing unless so it doesn't get missed, maybe something like this:
%i[to cc].select { |field| headers[field].present? }.each do |field|
Array(headers[filed]).each do |user|
#...
end
end

Podio Ruby Rails shows "nomethoderror"

I'm having problems with the Podio_rails_sample. I've included my leadsController and leads.rb files. The line that gets hung up is field['config']['settings']['allowed_values'].
Line 25 is the problematic one:
NoMethodError in LeadsController#new
undefined method `[]' for nil:NilClass
Extracted source (around line #25):
23 app = Podio::Application.find(APP_ID)
24 field = app.fields.find { |field| field['external_id'] == 'status' }
25 field['config']['settings']['allowed_values']
26 end
27
28 def self.create_from_params(params)
Rails.root: c:/Sites/podio_rails_sample
app = Podio::Application.find(APP_ID)
field = app.fields.find { |field| field['external_id'] == 'status' }
field['config']['settings']['allowed_values']
end
def self.create_from_params(params)
Rails.root: c:/Sites/podio_rails_sample
-----------------------------------
class LeadsController < ApplicationController
before_filter :load_collections, :only => [:new, :edit]
def index
#leads = Lead.all
end
def new
#lead = Lead.new
end
def create
Lead.create_from_params(params['lead'])
redirect_to leads_path, :notice => 'Lead created'
end
def edit
#lead = Lead.find_basic(params[:id])
end
def update
Lead.update_from_params(params[:id], params['lead'])
redirect_to leads_path, :notice => 'Lead updated'
end
def destroy
Lead.delete(params[:id])
redirect_to leads_path, :notice => 'Lead deleted'
end
#protected
def load_collections
#lead_contacts = Lead.space_contacts
#sales_contacts = Lead.users
#statuses = Lead.statuses
end
end
-------------------------------------
- leads.rb file
class Lead < Podio::Item
APP_ID =12328033
SPACE_ID =3204114
# Find all items in the Leads app
def self.all
collection = self.find_all(APP_ID)
collection[:all]
end
# Find valid lead contacts in the space
def self.space_contacts
Podio::Contact.find_all_for_space(SPACE_ID, :order => 'contact', :limit => 12, :contact_type => 'space,connection', :exclude_self => false) rescue []
end
# Find valid sales contacts in the space
def self.users
Podio::Contact.find_all_for_space(SPACE_ID, :order => 'contact', :limit => 12, :contact_type => 'user', :exclude_self => false) rescue []
end
# Find valid statuses
def self.statuses
app = Podio::Application.find(APP_ID)
field = app.fields.find { |field| field['external_id'] == 'status' }
field['config']['settings']['allowed_values']
end
def self.create_from_params(params)
# raise fields.inspect
self.create(APP_ID, { :fields => fields_from_params(params) })
end
def self.update_from_params(id, params)
self.update(id, { :fields => fields_from_params(params) })
end
#
# Map the field values return by the Podio API to simple getters
#
def organization
field_values_by_external_id('company-or-organisation', :simple => true)
end
def lead_contact
field_values_by_external_id('contacts', :simple => true).try(:[], 'name')
end
def sales_contact
field_values_by_external_id('sales-contact', :simple => true).try(:[], 'name')
end
def potential_revenue_value
field_values_by_external_id('potential-revenue').try(:first).try(:[], 'value').to_i
end
def potential_revenue_currency
field_values_by_external_id('potential-revenue').try(:first).try(:[], 'currency')
end
def probability
field_values_by_external_id('probability-of-sale', :simple => true)
end
def status
field_values_by_external_id('status', :simple => true)
end
def followup_at
field_values_by_external_id('next-follow-up').try(:first).try(:[], 'start').try(:to_datetime)
end
protected
def field_values_by_external_id(external_id, options = {})
if self.fields.present?
field = self.fields.find { |field| field['external_id'] == external_id }
if field
values = field['values']
if options[:simple]
values.first['value']
else
values
end
else
nil
end
else
nil
end
end
def self.fields_from_params(params)
{
'company-or-organisation' => params[:organization],
'contacts' => (params[:lead_contact].present? ? params[:lead_contact].to_i : nil),
'sales-contact' => (params[:sales_contact].present? ? params[:sales_contact].to_i : nil),
'potential-revenue' => { :value => params['potential_revenue_value'], :currency => params['potential_revenue_currency'] },
'probability-of-sale' => params[:probability].to_i,
'status' => params[:status],
'next-follow-up' => DateTime.new(params['followup_at(1i)'].to_i, params['followup_at(2i)'].to_i, params['followup_at(3i)'].to_i).to_s(:db)
}.delete_if { |k, v| v.nil? }
end
end

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!

Trying to push results of hash to an array - Ruby on rails

I'm just beginning to (hopefully!) learn programming / ruby on rails and trying to push the results of a hash to an array using:
ApplicationController:
def css_class
css = Array.new
product = {#product.oil => ' oil', #product.pressure_meters => ' pressure_meters', #product.commercial => 'commercial'}
product.each do |key, value|
if key == true
css.push(value)
end
end
сss.join
end
And this in the ProductsController:
def create
#product = Product.new(params[:product])
#product.css_class = css_class
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
format.html { render action: "new" }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
This only seems to only save the last thing that was pushed to the array, I tried the below code on it's own and it seems to work, so I'm baffled as to where I'm going wrong?
def css_class
css = Array.new
product = {1 => ' pressure_meters', 2 => ' oil'}
product.each do |key, value|
if key > 0
css.push(value)
end
end
css.join
end
puts css_class
Thanks in advance.
In Ruby Hash can't have duplicate keys so
def css_class
css = Array.new
product = { #product.oil => ' oil',
#product.pressure_meters => ' pressure_meters',
#product.commercial => 'commercial' }
product.each do |key, value|
if key == true
css.push(value)
end
end
сss.join
end
will not work because
irb(main):0> h = { true => 'foo', true => 'bar', false=>'foo', false => 'bar' }
=> {true=>"bar", false=>"bar"}
your second example works only because you have distinct keys (1,2) so let's refactor your code a bit
def css_class
css = ""
product = { ' oil' => #product.oil,
' pressure_meters' => #product.pressure_meters,
' commercial' => #product.commercial }
product.each do |key, value|
css << key if value
end
сss.strip
end
it can be simplified even more however previous version should work fine too
def css_class
[ "oil ", "pressure_meters ", "commercial " ].inject(""){ |sum, val| sum += val if #product.send( val.strip ) }.strip
end
You can use Hash#values to get an array of your hash's values.
So:
product_values = product.values
And conditionally, you could pick the ones you want using select, like this:
product_values = product.select {|k,v| k == true }.values
Which is verbose for:
product_values = product.select {|k,v| k }.values
Thanks for pointing me in the right direction. I kept getting a 500 internal server error with your code Bohdan, not sure why, but played around with it and eventually found this to work:
def css_class
css = Array.new
product = { ' oil' => #product.oil,
' pressure_meters' => #product.pressure_meters,
' commercial' => #product.commercial }
product.each do |key, value|
css << key if value
end
css.join
end

Resources