I want to pass an array from a Ruby file to another one.
I have a three files:
main.rb
company.rb
applicant.rb
Here is the code for main.rb:
require './src/company.rb'
require './src/applicant.rb'
company = Company.new('data/boundless.json')
company.find_applicants('google')
Here is the code for company.rb:
require 'json'
require_relative 'applicant.rb'
class Company
attr_accessor :jobs , :arrOfApp
def self.load_json(filepath)
file = File.read(filepath)
return JSON.parse(file)
end
def initialize(filePath)
# Load the json file and loop over the jobs to create an array of instance of `Job`
# Assign the `jobs` instance variable.
jobs=Array.new
data_hash = Company.load_json(filePath)
numberOfJobs= data_hash['jobs'].length
for i in 0 ... numberOfJobs
jobs[i]=data_hash['jobs'][i]['applicants']
# puts jobs
end
end
## TODO: Impelement this method to return applicants from all jobs with a
## tag matching this keyword
def find_applicants(keyWord)
app =Applicant.new
arrOfApp=Array.new
app.data_of_applicant(jobs)
end
end
And finally the code for applicant.rb:
require_relative 'company.rb'
class Applicant
attr_accessor :id, :name, :tags
def initialize
end
def data_of_applicant(j)
id=Array.new
name=Array.new
tags=Array.new
puts j
end
end
The program reads a JSON file to get some information from it. Whenever I try to print the value being sent to the applicant file nothing is printed.
You can't pass an array from a ruby file to another one., you only can pass data between classes and objects.
Other possibilities which may help:
constants (Defined with starting capital letter)
global variables (starting with $)
Singletons
To keep data inside the class instances (objects) you need attributes (variables starting with #).
You can find this concepts in every beginner manual of ruby (and if not, then the manual is not worth to be used)
You made another common error.
Let's check it with a small example:
class Company
attr_accessor :jobs
def initialize()
jobs='This should be assigned to my accessor jobs'
end
end
puts Company.new.jobs
The result is an empty line.
What happend? In the initialize-method you define a local variable jobs. Local means, it is only available in the method ans is lost when the method leaves.
Correct would be 1) using the instance variable:
class Company
attr_accessor :jobs
def initialize()
#jobs='This should be assigned to my accessor jobs'
end
end
or 2) using the accessor method:
class Company
attr_accessor :jobs
def initialize()
self.jobs='This should be assigned to my accessor jobs'
end
end
In both cases the puts Company.new.jobs returns the text you defined.
See also Ruby instance variable access
if i'm reading this correctly, you're asking ruby to make the calculation, but never stating that it should be printed. i believe changing the last line of your main.rb to this:
puts company.find_applicants('google')
should suffice.
Related
I have made a fake CRUD-based bank account manager that can create new accounts and also destroy them using the terminal. Just to make clear, this is not a Rails application. I've made a 'fake' MVC structure in vanilla Ruby to understand the basic concept.
I'm having difficulty trying to delete a class instance when the 'destroy' criteria has been satisfied. In this case, if a user wants to destroy a bank account, they need to specify the bank account number of the class instance. I'm not sure if my Ruby method is just incorrectly trying to handle the deletion or if what I am doing is not possible.
Here is method so far:
def delete(account_number)
#accounts.each_with_index do |account, index|
account.include?(account_number) ? account.delete_at(index) : "No account found"
end
end
Here is the error message I am being presented:
`block in delete': undefined method `include?' for #<Account:0x00007fe82c8926c0 #name="test", #account_number="12345", #sort_code="040004", #balance="1234.5"> (NoMethodError)
Essentially, my end goal is for my method to scan the class instance, match #account_number with the account_number passed in the terminal and delete the instance completely. I've been able to do this using 'index' i.e. "delete the 1st in the list" (index + 1) but want to try a more advanced way.
N.B: #accounts is an instance variable set as an array to store the instances.
I would use Array#delete_if when I want to delete an account with a certain name for an array of accounts.
def delete(account_number)
#accounts.delete_if { |account| account.number == account_number }
end
If there is no matching account found then this method keeps the #accounds array unchanged.
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
Hi i tried alot to push the variables which are in initialize to a class array but it always give error like this 'uninitialized class variable ##books in Object (NameError)'
code is here:
class Library
##books = Array.new
##books = []
attr_reader :bookName, :author
def initialize(bookName,author)
#bookName = bookName
#author = author
##books.push(self)
end
def countbooks
$count = 0
##books.each do |b|
count += 1
end
end
i used ##books.push(self) to take all variables of object to an array but it did not work.
DISCLAIMER: I haven't used Ruby for some time, so some of my knowledge might be outdated. Feel free to correct me.
There are multiple problems with your code. Let's get the code you provided into some sort of running state.
First of all, you are missing an end in your provided code within the countbooks method. It needs an end to close the loop. Second, you can use either Array.new or [] as they both create an empty array, but if you use both you just overwrite the first new, empty array.
If you now initialize an instance of your new class and call the method countbooks, you will receive an error because you don't access your global variable $count, but count instead, which has never been initialized. If you fix this, you can now just call the countbooks method and then a puts($count) and you get the current number.
However, global variables are generally bad practices, so you should avoid them. Remove the $ sign and add a return count after the loop ends, and we kind of replicated the Array.length method, just a lot slower :)
This should yield the following code:
class Library
##books = []
attr_reader :bookName, :author
def initialize(bookName,author)
#bookName = bookName
#author = author
##books.push(self)
end
def countbooks
count = 0
##books.each do |b|
count += 1
end
return count
end
end
The code should now be executable, but there is a big problem.
The class variable ##books:
You now have a single array for the whole class. That means, if we create a new instance of the class Library, the array will be shared across all of them.
x = Library.new("title","bintree")
y = Library.new("title2","bintree2")
puts(x.countbooks)
puts(y.bookName)
x.countsbooks will return 2 and you only push new entries to the array if you create a new instance of Library.
From your question, I understand that you now try to call ##books outside of the class. This will not work. It only exists in the Library class. You can not use the attr accessors to gain access, either.
You probably should read more on classes, instances, and especially the different variable types. These links helped me to answer your question:
More about classes, instances, and a bit about variables
More about arrays
Here is an example of how the code could look like if we ignore build-in functions such as Array.length to implement our own counter and avoid class (##) and global ($) variables. You can gain access to the #books if you now add attr_reader :books, but it would be better to write a method instead.
class LibraryBTD
def initialize
#books = Array.new
#counter = 0
end
def addBook(title,author)
#books.push([title,author])
#counter += 1
end
def listAllBooks
puts("There are currently " + #counter.to_s + " books in the library.")
#books.each do |book|
title = book[0]
author = book[1]
puts("Title: " + title + " | Author: " + author)
end
end
end
If we now create a new instance of LibraryBTD and add 2 books, we can list them with listAllBooks:
a = LibraryBTD.new
a.addBook("MyBook1","BTD")
a.addBook("MyBook2","BTD")
a.listAllBooks
This should output the following in the console:
There are currently 2 books in the library.
Title: MyBook1 | Author: BTD
Title: MyBook2 | Author: BTD
However, if we create a new instance, the list will be empty, because #books is only an instance variable.
b = LibraryBTD.new
b.listAllBooks
There are currently 0 books in the library.
When I assign variable names such as service_names and name_array they are nil and nothing goes to the class variable ##product_names.
I used Pry to try the code without storing it into a variable and it works. It has the values I need.
I had this split up in more variables before to make cleaner code, for example:
require 'pry'
require 'rubygems'
require 'open-uri'
require 'nokogiri'
class KefotoScraper::CLI
##product_names =[]
PAGE_URL = "https://kefotos.mx/"
def call
binding.pry
puts "These are the services that Kefoto offers:"
#list_products
puts "which service would you like to select?"
#selection = gets.chomp
view_price_range
puts "Would you like to go back to the service menu? y/n"
answer = gets.chomp
if answer == "y"
call
end
end
private
def home_html
# #home_html ||=
# HTTParty.get root_path
Nokogiri::HTML(open(PAGE_URL))
end
#
# # TODO: read about ruby memoization
# def home_node
#
# #home_node ||=
# Nokogiri::HTML(PAGE_URL)
# end
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
def list_products
i = 1
n = 0
while ##product_names.length < n
##product_names.each do |list_item|
puts "#{i} #{list_item[n]}"
i += 1
n += 1
end
end
end
def view_price_range
price_range = []
#service_links.each do |link|
if #service = link
link.css(".row").map {|price| price["p"].value}
price_range << p
end
price_range
end
def service_links
#service_links ||=
home_html.css(".nav-item").map { |link| link['href'] }
end
end
end
##product_names should contain the code that comes out of
home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "")
which later I turn back to an array.
This is what it looks like in Pry:
9] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")
=> ["[\"foto-enmarcada\"", " \"impresion-fotografica\"", " \"photobooks\"", " \"impresion-directa-canvas\"", " \"impresion-acrilico\"", " \"fotoregalos\"]"]
[10] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")[0]
=> "[\"foto-enmarcada\""
Nokogiri's command-line IRB is your friend. Use nokogiri "https://kefotos.mx/" at the shell to start it up:
irb(main):006:0> #doc.css('.nav-link[href]').map { |l| l['href'].sub(/\.php$/, '') }
=> ["foto-enmarcada", "impresion-fotografica", "photobooks", "impresion-directa-canvas", "impresion-acrilico", "fotoregalos"]
That tells us it's not dynamic HTML and shows how I'd retrieve those values. Since an a tag doesn't have to contain href parameters I guarded against retrieving any such tags by accident.
You've got bugs, potential bugs and bad practices. Here are some untested but likely to work ways to fix them:
Running the code results in:
uninitialized constant KefotoScraper (NameError)
In your code you have #service and #service_links which are never initialized so...?
Don't do this because it's cruel:
def home_html
Nokogiri::HTML(open(PAGE_URL))
end
Every time you call home_html you (re)open and (re)read the page from the remote site and wasting your and their CPU and network time. Instead, cache the parsed document in a variable kind of like you did in your commented-out line using HTTParty. It's much more friendly to not hit sites repeatedly and helps avoid getting banned.
Moving on:
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
I'd use something like get_product_names and return the array like I did in Nokogiri above:
def get_product_names
get_html.css('.nav-link[href]').map { |l|
l['href'].sub(/\.php$/, '')
}
end
:
:
##product_names = get_product_names()
Here's why I'd do it another way. You used:
link['href'].to_s.gsub(/.php/, "")
to_s is redundant because link['href'] is already returning a string. Stringizing a string wastes brain cycles when rereading/debugging the code. Be kind to yourself and don't do that.
require 'nokogiri'
html = '<a href="foo">'
doc = Nokogiri::HTML(html)
doc.at('a')['href'] # => "foo"
doc.at('a')['href'].class # => String
gsub Ew. How many occurrences of the target string do you anticipate to find and replace? If only one, which is extremely likely in a URL "href", instead use sub because it's more efficient; It only runs once and moves on whereas gsub looks through the string at least one additional time to see if it needs to run again.
/.php/ doesn't mean what you think it does, and it's a very subtle bug in waiting. /.php/ means "some character followed by "php", but you most likely meant "a period followed by 'php'". This was something I used to see all the time because other programmers I worked with didn't bother to figure out what they were doing, and being the senior guy it was my job to pick their code apart and find bugs. Instead you should use /\.php/ which removes the special meaning of ., resulting in your desired pattern which is not going to trigger if it encounters "aphp" or something similar. See "Metacharacters and Escapes" and the following section on that page for more information.
On top of the above, the pattern needs to be anchored to avoid wasting more CPU. /\.php/ will cause the regular expression engine to start at the beginning of the string and walk through it until it reaches the end. As strings get longer that process gets slower, and in production code that is processing GB of data it can slow down a system markedly. Instead, using an anchor like /\.php$/ or /\.php\z/ gives the engine a hint where it should start looking and can result in big speedups. I've got some answers on SO that go into this, and the included benchmarks show how they help. See "Anchors" for more information.
That should help you but I didn't try modifying your code to see if it did. When asking questions about bugs in your code we need the minimum code necessary to reproduce the problem. That lets us help you more quickly and efficiently. Please see "ask" and the linked pages and "mcve".
I have an array of objects with one instance variable:
oranges_array =
[#<Orange:0x007faade859ea0 #diameter=2.7>,
#<Orange:0x007faade859ef0 #diameter=2.8>,
#<Orange:0x007faade859f40 #diameter=2.6>]
For example, how would I access the diameter instance variables? I want to eventually get their average. Do I need to or am I on the right track in thinking I maybe use an each loop to shovel each diameter into its own array then use inject? Or is there a simpler way to access the diameter variables? Thanks!
One option is to add attr_reader to Orange class:
class Orange
attr_reader :diameter
end
Now you can get the avarage:
avg = oranges_array.map(&:diameter).inject(:+) / oranges_array.size.to_f
# with Ruby 2.4+ you can do the following:
# avg = oranges_array.sum(&:diameter) / oranges_array.size.to_f
Another option is to use Object#instance_variable_get (without having to add the attr_reader to the class:
avg = oranges_array.map { |orange| orange.instance_variable_get(:#diameter) }.inject(:+) / oranges_array.size.to_f
NOTE:
Using instance_variable_get for this task is the last resort option and I added it almost exclusively to show there's a way to get instance variables of objects in case there is, for example, no way to define attribute readers in the class.
I am new to Rails and am currently learning strong parameters in Rails 4 and following the below example from the official documentation:
`class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end`
Question 1:
What does current_account.people.find mean inside the update method?
Question 2:
Could someone please explain the person_params method. What is "params" inside the person_params method?
current_account is a most likely a private method that returns an Account instance. current_account.people.find(params[:id]) searches the people table for a person that belongs to the current_account and has an ID of params[:id]. Object#tap is a ruby method that yields a block with the current object, and then returns that object. In this case, the Person instance is updated inside the block and the returned from tap. Finally, redirect_to is a controller method that will redirect the request to a different path. redirect_to can take many different types of arguments, including an ActiveRecord model, a string, or a symbol. Passing it an ActiveRecord model will redirect the request to the model's resource path, which is defined in routes.rb. In this case, that path will most likely be /people/:id.
The params object is a hash containing parameter names and values. For example, the request /people?name=Joe&age=34 will result in the following params object: {name: 'Joe', age: '34'}.