ShadowingOuterLocalVariable rubocop error - ruby

here is my code, and here is my error. I think dont need to more descriptions here:
#terminal-error (error on object "= ->(object) do "
lib/form_object/base.rb:18:30: W: Lint/ShadowingOuterLocalVariable: Shadowing outer local variable - object.
need_validation = ->(object) do
def valid?
valid_attributes = []
attributes.each do |attribute_name, _attributes|
attribute_set = self.class.attribute_set[attribute_name]
object = self[attribute_name]
need_validation = ->(object) do
(object.class < FormObject::Base || attribute_set.options[:validate]) && object.respond_to?(:valid?)
end
if need_validation.call(object)
valid_attributes << object.valid?
elsif object.is_a?(Array)
object.each do |nested|
valid_attributes << nested.valid? if need_validation.call(nested)
end
end
end
valid_attributes << super
valid_attributes.all?
end

It's a warning from a lint that you run, which detects cases where you shadow (i.e. hide) another local variable, from an outer scope.
You have:
object = self[attribute_name]
need_validation = ->(object) do
(object.class < FormObject::Base || attribute_set.options[:validate]) && object.respond_to?(:valid?)
end
So the first variable object could not be referred to inside the lambda, as the argument is also called object.
You can remove this warning by simply renaming the parameter of your lambda:
need_validation = ->(obj) do
(obj.class < FormObject::Base || attribute_set.options[:validate]) && obj.respond_to?(:valid?)
end

Related

Use controller method in ActionCable channel

I have a rails api that I am trying to create a live stream feature for a git gui. I have a controller and a channel for commit updates and I want to use the live stream function inside the subscribe action in the channel. Is this possible? Or is there a better Rails way to do this. I think the correct way was to use ActionCable.server.broadcast('commit_updates', { body: return_data }) inside my controller method but I am not calling my controller method anywhere. Where/how do I call it so that it is run on a client subscribing to it? or should I place the logic inside the channel?
here is my commit_updates_controller.rb
class CommitUpdatesController < ApplicationController
include ActionController::Live
require 'listen'
require 'json'
$file_path = "#{Dir.getwd}/.git/objects"
##commits = {}
##commits_array = []
##edges_array = []
def index
end
def live_stream
# get folders exlcluding ".","..", "pack","info"
folders = Dir.entries($file_path).select {|folder| /^[a-z0-9]{2}$/i.match(folder) }
folders.each do |folder|
files = Dir.children("#{$file_path}/#{folder}")
files.each do |file|
CommitUpdate.find_commit_info(folder, file, ##commits_array)
end
end
generate_edges
ActionCable.server.broadcast('commit_updates', { body: return_data })
p return_data
# listens to any changes that happen to the git folder while the program is open
listener = Listen.to($file_path) do |modified, added, removed|
# puts(modified: modified, added: added, removed: removed)
added.each do |new_file_path|
split_new_file_path = new_file_path.split("/").reject!{|item| item.empty?}
folder = split_new_file_path[split_new_file_path.length() - 2]
file = split_new_file_path[split_new_file_path.length - 1]
CommitUpdate.find_commit_info(folder, file, ##commits_array)
add_edge(##edges_array, CommitUpdate.get_commit_info(folder, file))
ActionCable.server.broadcast('commit_updates', { body: return_data })
end
end
listener.start
sleep
end
private
def generate_edges
if ##commits_array.length != 0
##commits_array.each do |commit|
add_edge(##commits_array, commit)
end
end
end
def add_edge(array, commit)
if commit[:parents] != []
commit[:parents].each {|parent| ##edges_array.push({from: parent, to: commit[:sha1]})}
end
end
def return_data
ret = {
:nodes => ##commits_array,
:links => ##edges_array
}
return ret.to_json
end
end
here is my commit_updates_channel.rb
class CommitUpdatesChannel < ApplicationCable::Channel
def subscribed
stream_from 'commit_updates'
end
end
here is my commit_updates.rb Model
class CommitUpdate < ApplicationRecord
def self.find_commit_info(folder, file, array)
file_type = self.check_sha1_type(folder, file)
if file_type == "commit"
array.push(
self.get_commit_info(folder, file)
)
end
end
def self.get_commit_info(folder, file)
author = ""
parents = []
commit_message = ""
unixtime = ""
decoded_file = `git cat-file -p #{folder}#{file}`
file_data_array = decoded_file.split("\n").reject!{|item| item.empty?}
p file_data_array
file_data_array.each do |item|
split_item = item.split(" ")
case split_item[0]
when "author"
author = split_item[1..split_item.length() - 4].join(" ")
when "parent"
parents.push(split_item[1])
when "tree"
next
when "blob"
next
when "committer"
unixtime = split_item[split_item.length - 2]
else
commit_message = split_item.join(" ")
end
end
commit_info = {
:sha1 => "#{folder}#{file}",
:parents => parents,
:commit_message => commit_message,
:author => author,
:unixtime => unixtime,
:id => "#{folder}#{file}",
:label => "#{folder}#{file[0..4]}...",
:font => "18px verdana blue",
}
return commit_info
end
private
def self.check_sha1_type(folder, file)
return `git cat-file -t #{folder}#{file}`.chomp
end
end
To use controller's live_stream, you need to request this method, for example, write js programme that is calling it periodically. Or some bash script that is calling it. Not good.
Another solution is to put live_stream logic into background job that will check commit updates every X minute. And if job finds change, it broadcasts.
And more common is to place broadcasting in model callbacks after_save, after_commit. But it looks it is not your solution.

formal argument cannot be a class variable in ruby

I've never used Ruby before, and am attempting to run a program written
long ago. I've installed Ruby 2.4.1 and the gem package [test-unit
3.4.3] OK, but when I try to run it, I get an error:
tcreporter.rb:156: formal argument cannot be a class variable
##tc_array.each do |##tc|
^
Is there something in particular I'm doing wrong?
Below is the code snippet :
class TCReporter
##m = nil; ##c = nil; ##tc = nil; ##status = 'p'; ##notes = nil; ##tlArr = []
##resultMapping = {'p'=>'PASS', 'f'=>'FAIL', 'b'=>'BLOCKED', 's'=>'SKIP','pr'=>'PREQFAIL', 'con'=>'CONERR', 'h'=>'HWSKIP', 'cor'=>'CORE'}
def self.report_result(currentTest, status, notes=nil)
if $trInit
##m = currentTest.split('(')[0] ###m is the test METHOD currently being executed in the framework.
##c = currentTest.split('(')[1].split(')')[0] ###c is the test CLASS currently being executed in the framework
if ##c =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i #If there's a mapping on the test class then report a test class result.
##tc = ##c.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i)[0].upcase.sub('_', '-') #Get the TR class mapping
#When reporting at the test class level, the status always starts out as 'p' (PASS). If there's any
#non-passing status for any test method within the test class (blocked or failed) then use that result
#for reporting. Once the global status '##status' has been updated once then no more updating occurs.
if ##status == 'p' && status != 'p'
##status = status
##notes = notes
end
if eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m && eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #The first test method is the last test method. All done, do a TestLink update.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m #A new test class is being evaluated. Set everything to default except status (use whatever the first test class returned).
##m = nil; ##c = nil; ##tc = nil; ##status = status
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #Done with the test class. Time to report the test result.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
else #The test class is still being executed. Don't update TestLink yet, just check for a non-passing result.
if ##status == 'p' && status != 'p' #Update the test status if it's a non-pass result. Otherwise, use the earlier failed or blocked status.
##status = status
end
end
end
#If there's a mapping on a test method then report a test method result.
if ##m =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i
##tc_array = ##m.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i)[0].upcase.sub('_', '-').split("_")
if ##tc_array.size > 1
tmp_prefix = ##tc_array[0].split("-").first
tmp_array = []
##tc_array.each do|tmp|
tmp_array << tmp_prefix + "-" + tmp.split("-").last
end
##tc_array = tmp_array
end
##tc_array.each do |##tc|
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, status, notes)
puts status
rescue => e
puts e
ensure
if result && !result.success
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
end
end
end
end
Thanks in advance
This is fixed now after making "tc" as local variable.

Ruby method variable declaration

I'm trying to define methods to parse through an apache log file and pull ip addresses, URLs, requests per hour, and error codes. I've got everything working outside of methods, but when attempting to put that code into the methods I keep getting the error message "Stack level too deep." Here is the code in question.
class CommonLog
def initialize(logfile)
#logfile = logfile
end
def readfile
#readfile = File.readlines(#logfile).map { |line|
line.split()
}
#readfile = #readfile.to_s.split(" ")
end
def ip_histogram
#ip_count = 0
#readfile.each_index { |index|
if (#readfile[index] =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ )
puts #readfile[index]
puts #ip_count += 1
end
}
end
def url_histogram
url_count = 0
cleaned_file.each_index { |index|
if (cleaned_file[index] =~ /\/{1}(([a-z]{4,})|(\~{1}))\:{0}\S+/ )
puts cleaned_file[index]
puts url_count += 1
end
}
end
def requests_per_hour
end
def sorted_list
end
end
my_file = CommonLog.new("test_log")
cleaned_file = my_file.readfile
puts cleaned_file.ip_histogram
It looks like the problem lies on you CommonLog#readfile method:
def readfile
#readfile = File.readlines(#logfile).map { |line|
line.split()
}
#readfile = readfile.to_s.split(" ")
end
Notice that inside the implementation of readfile your calling readfile recursively? When it executes it reads the lines from the file, maps them and assign the result the #readfile; then it calls readfile and the method starts to execute again; this goes on forever, until you stack blows up because of too many recursive method calls.
I assume what you actually meant is:
#readfile = #readfile.to_s.split(" ")

Calling multiple methods on a CSV object

I have constructed an Event Manager class that performs parsing actions on a CSV file, and produces html letters using erb. It is part of a jumpstart labs tutorial
The program works fine, but I am unable to call multiple methods on an object without the earlier methods interfering with the later methods. As a result, I have opted to create multiple objects to call instance methods on, which seems like a clunky inelegant solution. Is there a better way to do this, where I can create a single new object and call methods on it?
Like so:
eventmg = EventManager.new("event_attendees.csv")
eventmg.print_valid_phone_numbers
eventmg_2 = EventManager.new("event_attendees.csv")
eventmg_2.print_zipcodes
eventmg_3 = EventManager.new("event_attendees.csv")
eventmg_3.time_targeter
eventmg_4 = EventManager.new("event_attendees.csv")
eventmg_4.day_of_week
eventmg_5 = EventManager.new("event_attendees.csv")
eventmg_5.create_thank_you_letters
The complete code is as follows
require 'csv'
require 'sunlight/congress'
require 'erb'
class EventManager
INVALID_PHONE_NUMBER = "0000000000"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def initialize(file_name, list_selections = [])
puts "EventManager Initialized."
#file = CSV.open(file_name, {:headers => true,
:header_converters => :symbol} )
#list_selections = list_selections
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
def print_zipcodes
puts "Valid Participant Zipcodes"
#file.each do |line|
zipcode = clean_zipcode(line[:zipcode])
puts zipcode
end
end
def clean_phone(phone_number)
converted = phone_number.scan(/\d/).join('').split('')
if converted.count == 10
phone_number
elsif phone_number.to_s.length < 10
INVALID_PHONE_NUMBER
elsif phone_number.to_s.length == 11 && converted[0] == 1
phone_number.shift
phone_number.join('')
elsif phone_number.to_s.length == 11 && converted[0] != 1
INVALID_PHONE_NUMBER
else
phone_number.to_s.length > 11
INVALID_PHONE_NUMBER
end
end
def print_valid_phone_numbers
puts "Valid Participant Phone Numbers"
#file.each do |line|
clean_number = clean_phone(line[:homephone])
puts clean_number
end
end
def time_targeter
busy_times = Array.new(24) {0}
#file.each do |line|
registration = line[:regdate]
prepped_time = DateTime.strptime(registration, "%m/%d/%Y %H:%M")
prepped_time = prepped_time.hour.to_i
# inserts filtered hour into the array 'list_selections'
#list_selections << prepped_time
end
# tallies number of registrations for each hour
i = 0
while i < #list_selections.count
busy_times[#list_selections[i]] += 1
i+=1
end
# delivers a result showing the hour and the number of registrations
puts "Number of Registered Participants by Hour:"
busy_times.each_with_index {|counter, hours| puts "#{hours}\t#{counter}"}
end
def day_of_week
busy_day = Array.new(7) {0}
d_of_w = ["Monday:", "Tuesday:", "Wednesday:", "Thursday:", "Friday:", "Saturday:", "Sunday:"]
#file.each do |line|
registration = line[:regdate]
# you have to reformat date because of parser format
prepped_date = Date.strptime(registration, "%m/%d/%y")
prepped_date = prepped_date.wday
# adds filtered day of week into array 'list selections'
#list_selections << prepped_date
end
i = 0
while i < #list_selections.count
# i is minus one since days of week begin at '1' and arrays begin at '0'
busy_day[#list_selections[i-1]] += 1
i+=1
end
#busy_day.each_with_index {|counter, day| puts "#{day}\t#{counter}"}
prepared = d_of_w.zip(busy_day)
puts "Number of Registered Participants by Day of Week"
prepared.each{|date| puts date.join(" ")}
end
def legislators_by_zipcode(zipcode)
Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def save_thank_you_letters(id,form_letter)
Dir.mkdir("output") unless Dir.exists?("output")
filename = "output/thanks_#{id}.html"
File.open(filename,'w') do |file|
file.puts form_letter
end
end
def create_thank_you_letters
puts "Thank You Letters Available in Output Folder"
template_letter = File.read "form_letter.erb"
erb_template = ERB.new template_letter
#file.each do |line|
id = line[0]
name = line[:first_name]
zipcode = clean_zipcode(line[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id,form_letter)
end
end
end
The reason you're experiencing this problem is because when you apply each to the result of CSV.open you're moving the file pointer each time. When you get to the end of the file with one of your methods, there is nothing for anyone else to read.
An alternative is to read the contents of the file into an instance variable at initialization with readlines. You'll get an array of arrays which you can operate on with each just as easily.
"Is there a better way to do this, where I can create a single new object and call methods on it?"
Probably. If your methods are interfering with one another, it means you're changing state within the manager, instead of working on local variables.
Sometimes, it's the right thing to do (e.g. Array#<<); sometimes not (e.g. Fixnum#+)... Seeing your method names, it probably isn't.
Nail the offenders down and adjust the code accordingly. (I only scanned your code, but those Array#<< calls on an instance variable, in particular, look fishy.)

How to return a particular value from a method?

I have this code that tries to return a value from a method:
temp = "123"
return temp
and I have this line that calls the method and assigns the return value:
person_connections = #client.get_person_connections(:id => current_user_id )
but when I try to inspect person_connections, it shows some different object string. Any idea how to return the actual value of the temp variable?
def get_person_connections(options = {})
person_id = options[:id]
path = "/people/id=" + person_id + ":(num-connections)"
query_connections(path, options)
self
end
and
private
def query_connections(path, options={})
fields = options.delete(:fields) || LinkedIn.default_profile_fields
if options.delete(:public)
path +=":public"
elsif fields
path +=":(#{fields.map{ |f| f.to_s.gsub("_","-") }.join(',')})"
end
headers = options.delete(:headers) || {}
params = options.map { |k,v| v.is_a?(Array) ? v.map{|i| "#{k}=#{i}"}.join("&") : "#{k}=#{v}" }.join("&")
path += "?#{params}" if not params.empty?
temp_var = get(path, headers)
hash = JSON.parse(temp_var)
conn = hash["numConnections"]
end
As Samy said in a comment:
In Ruby, the last statement will be returned.
So if we take a look at get_person_connections, we see that the last line is self. What it means is that it returns the instance on which the method was called, #client in this case.
Additional notes: the solution would be to remove self, although if the method is used elsewhere be careful as returning self is often used to allow chaining of methods (though it hardly makes sense to do that on a get method).

Resources