Undefined Method in Rails even though the method exist - ruby-on-rails

I have a method in my view helper directory that I am trying to use within a model but i keep getting a undefined method error. I cannot figure out what i am doing wrong.This is my module.
module StbHelper
def gen_csv(stbs)
CSV.generate do |csv|
csv << [
'param1',
'param2'
]
stbs.each do |stb|
health_check = stb.stb_health_checks.last
csv << [
'value1',
'value2'
]
end
end
end
This is the class i want to use the method in.
require 'stb_helper'
class Stb < ActiveRecord::Base
def self.get_notes_data
.
.
.
end
def self.update
.
.
.
end
def self.report(options={})
csv_file = nil
if options == {}
########################################
# This is the line that throws the error
csv_file = StbHelper.gen_csv(Stb.all)
#######################################
else
stbs = []
customers = List.where(id: options[:list])[0].customers
customers.each do |customer|
customer.accounts.each do |account|
stbs += account.stbs
end
end
csv_file = StbHelper.gen_csv(stbs)
end
end
end

You've defined a module, that doesn't require instantiation. You should be able to use it without the StbHelper part (as long as you require the module in the document):
def self.report(options={})
csv_file = nil
if options == {}
########################################
# This is the line that throws the error
csv_file = gen_csv(Stb.all)
#######################################
else
stbs = []
customers = List.where(id: options[:list])[0].customers
customers.each do |customer|
customer.accounts.each do |account|
stbs += account.stbs
end
end
csv_file = gen_csv(stbs)
end
end
But you shouldn't use a helper for this, you can create a normal module and require it the same way.
Edit: Save the module in a new folder called app/modules (and restart the server), save a file called stb_helper.rb with the contents of your module:
module StbHelper
def gen_csv(stbs)
CSV.generate do |csv|
csv << [
'param1',
'param2'
]
stbs.each do |stb|
health_check = stb.stb_health_checks.last
csv << [
'value1',
'value2'
]
end
end
end

Related

undefined method `map' api request [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I followed tutorial how to integrate 3rd party api with a ruby on rails but I get an error
undefined method `map' for
{"number"=>12} permitted: false>:ActionController::Parameters
which points to request.rb
query_string = query.map{|k,v| "#{k}=#{v}"}.join("&")
Full code
recipes_controller.rb
class RecipesController < ApplicationController
def index
#tag = query.fetch(:tags, 'all')
#refresh_params = refresh_params
#recipes, #errors = Spoonacular::Recipe.random(query, clear_cache)
end
def show
#recipe = Spoonacular::Recipe.find(params[:id])
end
private
def query
params.permit(:query).fetch(:query, {})
end
def clear_cache
params[:clear_cache].present?
end
def refresh_params
refresh = { clear_cache: true }
refresh.merge!({ query: query }) if query.present?
refresh
end
end
app/services/spoonacular/recipes.rb
module Spoonacular
class Recipe < Base
attr_accessor :aggregate_likes,
:dairy_free,
:gluten_free,
:id,
:image,
:ingredients,
:instructions,
:ready_in_minutes,
:title,
:vegan,
:vegetarian
MAX_LIMIT = 12
CACHE_DEFAULTS = { expires_in: 7.days, force: false }
def self.random(query = {}, clear_cache)
cache = CACHE_DEFAULTS.merge({ force: clear_cache })
response = Spoonacular::Request.where('recipes/random', cache, query.merge({ number: MAX_LIMIT }))
recipes = response.fetch('recipes', []).map { |recipe| Recipe.new(recipe) }
[ recipes, response[:errors] ]
end
def self.find(id)
response = Spoonacular::Request.get("recipes/#{id}/information", CACHE_DEFAULTS)
Recipe.new(response)
end
def initialize(args = {})
super(args)
self.ingredients = parse_ingredients(args)
self.instructions = parse_instructions(args)
end
def parse_ingredients(args = {})
args.fetch("extendedIngredients", []).map { |ingredient| Ingredient.new(ingredient) }
end
def parse_instructions(args = {})
instructions = args.fetch("analyzedInstructions", [])
if instructions.present?
steps = instructions.first.fetch("steps", [])
steps.map { |instruction| Instruction.new(instruction) }
else
[]
end
end
end
end
app/services/spoonacular/base.rb
module Spoonacular
class Base
attr_accessor :errors
def initialize(args = {})
args.each do |name, value|
attr_name = name.to_s.underscore
send("#{attr_name}=", value) if respond_to?("#{attr_name}=")
end
end
end
end
app/services/spoonacular/request.rb
module Spoonacular
class Request
class << self
def where(resource_path, cache, query = {}, options = {})
response, status = get_json(resource_path, cache, query)
status == 200 ? response : errors(response)
end
def get(id, cache)
response, status = get_json(id, cache)
status == 200 ? response : errors(response)
end
def errors(response)
error = { errors: { status: response["status"], message: response["message"] } }
response.merge(error)
end
def get_json(root_path, cache, query = {})
query_string = query.map{|k,v| "#{k}=#{v}"}.join("&")
path = query.empty?? root_path : "#{root_path}?#{query_string}"
response = Rails.cache.fetch(path, expires_in: cache[:expires_in], force: cache[:force]) do
api.get(path)
end
[JSON.parse(response.body), response.status]
end
def api
Connection.api
end
end
end
end
app/services/spoonacular/connection.rb
require 'faraday'
require 'json'
module Spoonacular
class Connection
BASE = 'https://spoonacular-recipe-food-nutrition-v1.p.mashape.com'
def self.api
Faraday.new(url: BASE) do |faraday|
faraday.response :logger
faraday.adapter Faraday.default_adapter
faraday.headers['Content-Type'] = 'application/json'
faraday.headers['X-Mashape-Key'] ='key'
end
end
end
end
Thank you for any help.
You have 2 separate errors here.
uninitialized constant Spoonacular::Recipe::Request
This one you can fix by explicitly setting top-level scope for Request class:
::Request.where(...)
It applies if you keep Request file in app/spoonacular/request.rb. But I suggest to move it to app/services/spoonacular/ where all your other spoonacular related classes are. So in this case you need to encircle class Request in module Spoonacular. After that you can call it like that:
Spoonacular::Request.where(...)
Same goes for class Connection.
SO answer about scope resolution operator
undefined method `map' for {"number"=>12} permitted:
false>:ActionController::Parameters
This one comes from private query method in recipes_controller.rb. params is ActionController::Parameters object and in order to retrieve values from it you need to permit them first:
def query
params.permit(:query).to_h
end
Now it should return Hash object.
Here is detailed answer on SO about that
RubyOnRails Guide about strong params

wrapping code that runs in Rails environment into a module

I have this code and it works perfectly
require "date"
#past = []
#future = []
#artist = Artist.find(2)
def sort_by_date(artist)
artist.events.each do |event|
if event.date < DateTime.now
#past << event.id
else
#future << event.id
end
end
end
def event_title(arr)
arr.each do |event_id|
e = Event.find(event_id)
artist_names = []
e.artists.each do |artist|
unless artist.name == #artist.name
artist_names << artist.name
end
end
puts "#{e.name} with #{artist_names.join(", ")} at #{(Venue.find(e.venue_id)).name}"
end
end
sort_by_date(#artist)
puts "Upcoming Events: "
event_title(#future)
puts "Past Events: "
event_title(#past)
I want to run wrap this operation into a module, but I'm having trouble understanding how to pass artist_id to it properly. With this command rails runner app/modules/artist_event_sort.rb, I'm getting this error: ``': undefined method sort_by_date' for SortedArtistEvents:Module (NoMethodError). The two methods sort_by_date and event_title worked as they should before I tried wrapping this whole operation up into a module, so that's where I know I've missed something.
module SortedArtistEvents
require "date"
attr_accessor :artist_id
def initialize(artist_id)
#past = []
#future = []
#artist = Artist.find(artist_id)
end
def sort_by_date(artist)
artist.events.each do |event|
if event.date < DateTime.now
#past << event.id
else
#future << event.id
end
end
end
def event_title(arr)
arr.each do |event_id|
e = Event.find(event_id)
artist_names = []
e.artists.each do |artist|
unless artist.name == #artist.name
artist_names << artist.name
end
end
puts "#{e.name} with #{artist_names.join(", ")} at #{(Venue.find(e.venue_id)).name}"
end
end
sort_by_date(#artist)
puts "Upcoming Events: "
self.event_title(#future)
puts "Past Events: "
event_title(#past)
end
class LetsSort
include SortedArtistEvents
end
test_artist_sort = LetsSort.new(2)
It looks like there are a couple things wrong here. You are trying to initialize a module, you can only initialize a class, e.g. class SortedArtistEvents.
If you have this:
module Foo
def bar; end
end
bar is only accessible by including or extending a module or class with Foo. With your error undefined method sort_by_date' for SortedArtistEvents:Module you would have to do
module SortedArtistsEvents
def self.sort_by_date; end
end
to get behavior like SortedArtistsEvents.sort_by_date

Named scope with multiple values

I'm having some trouble with my named scope.
def self.by_status(status)
arr = status.split(',').map{ |s| s }
logger.debug "RESULT: #{arr.inspect}"
where(status: arr)
end
When I call this scope with more than one value, the result of arr = ["New", "Open"]
This does not return any results, while it should. If I try this command in the console: Shipment.where(status: ['New', 'Open']) I get the results that I'm expecting.
Am I missing something here?
Edit (added the call of the class method ):
def self.to_csv(options = {}, vendor_id, status)
CSV.generate(options) do |csv|
csv << column_names
if !vendor_id.blank? && status.blank?
by_vendor_id(vendor_id).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif !vendor_id.blank? && !status.blank?
by_vendor_id(vendor_id).by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif vendor_id.blank? && !status.blank?
logger.debug "by_status result: #{by_status(status).inspect}"
by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
else
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
end
Try this in your model:
scope :by_status, ->(*statuses) { where(status: statuses) }
Then in your code you can call:
Shipment.by_status('New', 'Open')
This has the flexibility to just take one argument, too:
Shipment.by_status('New')

Creating Message Object Parser

I'm receiving results from a web service like this:
result.body returns:
[2] pry(#<User::EmailSettingsController>)> result.body
=> {"RESULT"=>
{"MESSAGES"=>
[{"MESSAGE"=>
{"TYPE"=>"E",
"ID"=>"HRRCF_WD_UI",
"NUMBER"=>"025",
"MESSAGE"=>"U kunt maximaal \"5\" jobagents creëren 1",
"LOG_NO"=>"",
"LOG_MSG_NO"=>"000000",
"MESSAGE_V1"=>"5",
"MESSAGE_V2"=>"1",
"MESSAGE_V3"=>"",
"MESSAGE_V4"=>"",
"PARAMETER"=>"",
"ROW"=>"0",
"FIELD"=>"",
"SYSTEM"=>""}},
{"MESSAGE"=>
{"TYPE"=>"E",
"ID"=>"HRRCF_WD_UI",
"NUMBER"=>"025",
"MESSAGE"=>"U kunt maximaal \"5\" jobagents creëren 2",
"LOG_NO"=>"",
"LOG_MSG_NO"=>"000000",
"MESSAGE_V1"=>"5",
"MESSAGE_V2"=>"2",
"MESSAGE_V3"=>"",
"MESSAGE_V4"=>"",
"PARAMETER"=>"",
"ROW"=>"0",
"FIELD"=>"",
"SYSTEM"=>""}},
{"MESSAGE"=>
{"TYPE"=>"E",
"ID"=>"HRRCF_WD_UI",
"NUMBER"=>"025",
"MESSAGE"=>"U kunt maximaal \"5\" jobagents creëren 3",
"LOG_NO"=>"",
"LOG_MSG_NO"=>"000000",
"MESSAGE_V1"=>"5",
"MESSAGE_V2"=>"3",
"MESSAGE_V3"=>"",
"MESSAGE_V4"=>"",
"PARAMETER"=>"",
"ROW"=>"0",
"FIELD"=>"",
"SYSTEM"=>""}}]}}
Is it possible to create something ParseMessageObject(result.body) that returns that I can do something like this.
message_list = ParseMessageObject(result.body)
message_list.each do |message|
puts message.message
puts message.type
end
I have no idea if this is possible or how to do this any suggestions to get me started are welcome!
EDIT 1:
Created my class in lib:
class MessageParser
def self.parse(result)
end
end
This should basically do what you want, using a simple open struct to create a message class which has accessors for each of the keys in your message hash
require 'ostruct'
class MessageParser
Message = Struct.new(:type, :id, :number, :message, :log_no, :log_msg_no, :message_v1, :message_v2, :message_v3, :message_v4, :parameter, :row, :field, :system)
attr_reader :messages
def initialize(data)
#data = data.fetch("MESSAGES",[])
#messages = []
parse_data
end
private
def parse_data
#data.each do | msg |
message = Message.new
msg.fetch("MESSAGE",{}).each do |key, value|
message[key.downcase.to_sym] = value
end
#messages << message
end
end
end
parser = MessageParser.new(result.body["RESULT"])
parser.messages.each do |message|
puts message.message
puts message.type
end
Something like this should work:
class ParsedMessages
include Enumerable
attr_reader :messages
def initialize(data)
#messages = extract_messages_from_data(data)
end
def extract_messages_from_data(data)
# TODO: Parse data and return message objects
end
def each &block
#messages.each do |message|
if block_given?
block.call message
else
yield message
end
end
end
end
Now you can use all methods from Enumerable on ParsedMessages, like each, find, map etc etc.

Undefined Method when running rspec test using a stub

I have a model which has a to_csv method and import method, trying to test this in rspec to make sure it does the right thing but having a problem. I get the following error:
Failures:
1) Category Class import should create a new record if id does not exist
Failure/Error: Category.import("filename", product)
NoMethodError:
undefined method `path' for "filename":String
Model:
class Category
...<snip>
def self.import(file, product)
product = Product.find(product)
CSV.foreach(file.path, headers: true, col_sep: ";") do |row|
row = row.to_hash
row["variations"] = row["variations"].split(",").map { |s| s.strip }
category = product.categories.find(row["id"]) || Category.new(row)
if category.new_record?
product.categories << category
else
category.update_attributes(row)
end
end
end
def self.to_csv(product, options = {})
product = Product.find(product)
CSV.generate(col_sep: ";") do |csv|
csv << ['id','title','description','variations']
product.categories.each do |category|
variations = category.variations.join(',')
csv << [category.id, category.title, category.description, variations]
end
end
end
end
My Test:
describe Category do
describe 'Class' do
subject { Category }
it { should respond_to(:import) }
it { should respond_to(:to_csv) }
let(:data) { "id;title;description;variations\r1;a title;;abd" }
describe 'import' do
it "should create a new record if id does not exist" do
product = create(:product)
File.stub(:open).with("filename","rb") { StringIO.new(data) }
Category.import("filename", product)
end
end
end
end
I would just make Category.import take a filename:
Category.import("filename", product)
Then Category.import just passes this filename to the CSV.foreach call:
CSV.foreach(filename, headers: true, col_sep: ";") do |row|
It's then not necessary to stub File.open or any of that jazz.

Resources