Intitalizing Object with Array of objects from another class Ruby - ruby-on-rails

I have created a small Ruby class here:
class Star
#Star initialization
def initialize(star, number)
#star = star
#number = number
end
end
and I am looking to initialize a class called Solar System with 100 stars. This is what I have done and it doesn't seem to be working. Any help would be greatly appreciated.
require_relative 'star.rb'
class SolarSystem
#Initialize Game
def initialize(partOfSolarSystem)
#partOfSolarSystem = partOfSolarSystem
#stars_array = []
for i in 0..99
stars_array = Star.new('unknown_star',i)
end
end
def show_solar_system
#code here to show all the initialized stars in solar system
end
end
I can't seem to get it to initialize the array in the constructor. I would then like to be able to print out all of the elements in the stars array. Any help with this would be greatly appreciated.
Also in an effort to eventually move this to a database with rails or something of that nature, should I be looking to hash this or will this be easily converted to mySQL or another DB with some helper functions? I would eventually like to write this into rails and have a dynamic website for it.
Once again, thanks very much.

Your problem is assigning a new value to #stars_array variable on each iteration. There are multiple ways to deal with it:
#stars_array = (0..99).map { |i| Star.new('unknown_star',i) }
By the way, there is a couple of design issues (just for your attention):
Why variable is called stars_array, not just stars?
Why would ever instance of Star class have some object named #star inside? Recursion? :) Seems like #name would be proper and more clear attribute's name.
Don't miss indentation.
EDIT: About DB-mapping. Most common way - inherit both classes from ActiveRecord::Base, and create one-to-many relation from solar system to stars. Each class will have it's own table. Takes absolutely no efforts.

You are assigning the new object every time round the loop. The fix is to append the new object:
#stars_array << Star.new('unknown_star',i)
Or, if you prefer words rather than symbols:
#stars_array.push(Star.new('unknown_star',i))
Or, to be more terse:
100.times {|i| #stars_array << Star.new('unknown_star',i) }

A few things to fix to make it work. In your loop you're assigning a new value to the array rather than appending to it. Secondly, in your loop you're using a local variable stars_array instead of the instance variable #stars_array.
Your initialize method should look like this:
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
for i in 0..99
#stars_array << Star.new('unknown_star', i)
end
end
Also, you might want to revisit your Ruby idioms, like preferring snake_case to camelCase for variable names and avoiding for loops in favor of each, e.g.
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
(0..99).each { |i| #stars_array << Star.new('unknown_star', i) }
end

Related

Need to iterate through a instance of a rails database like an array

I have a relation in my rails database and I get one instance of my relation. I then need to determine how much of that instance is filled in. I do this by counting the nils in the relation but I don't know how to do this through code. I know about the .each loop but that makes me state the fields and i need something more like an array. This is what i have so far
#survey_data = Surveyprofile.find_by(:user_id => #user.user_id)
#counter = 0
#index = 0
#survey_data.each do |d|
//i need something like
if d[index].nil? == false
#counter = #counter +1
end
#index++
end
does anyone know how to express this??
(this is all done in the controller by the way)
I haven't tested this approach, but it should work.
survey_profile.rb:
class SurveyProfile < ActiveRecord::Base
def self.number_of_empty_answers(user_id)
user_survey = SurveyProfile.find_by_user_id(user_id)
count = 0
user_survey.attributes.each do |attr_name, attr_value|
count += 1 if attr_value.nil?
end
return count
end
survey_profiles_controller.rb:
SurveyProfile.number_of_empty_answers(#user.id)
The class method number_of_empty_answers counts the answers the user has not filled. Then you call that method from your controller.
Note that it doesn't have to be a class method. It can also be an instance method. You can find the instance first in the controller and then call the method on it.
Since you're iterating through the attributes hash, you can get more fancy and build an array of field names that have not been empty. But if you just want the count, the above should work.

Rails - dynamic method creation

I have two methods that are identical apart from the ActiveRecord class they are referencing:
def category_id_find(category_name)
category = Category.find_by_name(category_name)
if category != nil
return category.id
else
return nil
end
end
def brand_id_find(brand)
brand = Brand.find_by_name(brand)
if brand != nil
return brand.id
else
return nil
end
end
Now, I just know there must be a more Railsy/Ruby way to combine this into some kind of dynamically-created method that takes two arguments, the class and the string to find, so I tried (and failed) with something like this:
def id_find(class, to_find)
thing = (class.capitalize).find_by_name(to_find)
if thing.id != nil
return thing.id
else
return nil
end
end
which means I could call id_find(category, "Sports")
I am having to populate tables during seeding from a single, monster CSV file which contains all the data. So, for example, I am having to grab all the distinct categories from the CSV, punt them in a Category table then then assign each item's category_id based on the id from the just-populated category table, if that makes sense...
class is a reserved keyword in Ruby (it's used for class declarations only), so you can't use it to name your method parameter. Developers often change it to klass, which preserves the original meaning without colliding with this restriction. However, in this case, you'll probably be passing in the name of a class as a string, so I would call it class_name.
Rails' ActiveSupport has a number of built in inflection methods that you can use to turn a string into a constant. Depending on what your CSV data looks like, you might end up with something like this:
def id_find(class_name, to_find)
thing = (class_name.camelize.constantize).find_by_name(to_find)
...
end
If using a string, you can use constantize instead of capitalize and your code should work (in theory):
thing = passed_in_class.constantize.find_by_name(to_find)
But you can also pass the actual class itself to the method, no reason not to:
thing = passed_in_class.find_by_name(to_find)

Rails/Ruby one-liner unless zero/nil?

Is there a way to make this situation more compact in rails views?
Eg I have haml
= object.count unless object.count ==0
I sort of don't like that has I'm repeating the function there, I would much rather have something like
= object.count unless ==0
Eg if I had more complex statements
= object.relations.where(attribute: "something").count unless zero?
I could split that into two lines say
- cnt = object.relations.where(attribute: "something").count
= cnt unless cnt==0
But for each situation I would have multiple lines, and storing a variable to use once sucks.
EDIT: just to elaborate I want to check if the number is 0, and if so not display anything. It looks nicer in the view that way.
UPDATE:
One of the answers made come up with a solution along these lines
class Object
def unless
self unless yield(self)
end
end
So I can call whatever object I have with a block eg. .unless{|c| c<1}
This lets me tack the conditionals on, and keeps it pretty clear what is going on :), bonus is as it's block driven I can use this on any object :P.
Thanks everyone :)
UPDATE EVEN MORE
Having |c| in the block sucked. So I looked up the api and changed it too
class Object
def unless(&block)
self unless instance_eval(&block)
end
end
So now I can use .count.unless{zero?} to accomplish this :P. Or if I have a complicated condition I can add that in with |c| etc.
If object is an array you can use object.empty? (or object.any? for the reverse case)
Just create a view helper:
def display_count_or_nothing(array)
array.count unless array.count == 0
end
In the view you can use it like this:
<%= display_count_or_nothing(array) %>
i think the following is nice and clear, although i hate the variable "object",
it would be much nicer if the name of the variable described the contents of the array (as plural)
= object.count unless object.empty?
If this is only about count, you can monkey patch Enumerable:
module Enumerable
def count_or_empty_string
self.any? ? self.count : ''
end
end
If object is an enumerable, you can do this:
= object.count_or_empty_string
This will return an "" if object.count == 0 else it will return an integer. So there is no need for unless or if in your HAML anymore.

How to refactor this Ruby (controller) code?

This is the code in my reports controller, it just looks so bad, can anyone give me some suggestions on how to tidy it up?
# app\controller\reports_controller.rb
#report_lines = []
#sum_wp, #sum_projcted_wp, #sum_il, #sum_projcted_il, #sum_li,#sum_gross_profit ,#sum_opportunities = [0,0,0,0,0,0,0]
date = #start_date
num_of_months.times do
wp,projected_wp, invoice_line,projected_il,line_item, opp = Report.data_of_invoicing_and_delivery_report(#part_or_service,date)
#sum_wp += wp
#sum_projcted_wp +=projected_wp
#sum_il=invoice_line
#sum_projcted_il +=projected_il
#sum_li += line_item
gross_profit = invoice_line - line_item
#sum_gross_profit += gross_profit
#sum_opportunities += opp
#report_lines << [date.strftime("%m/%Y"),wp,projected_wp ,invoice_line,projected_il,line_item,gross_profit,opp]
date = date.next_month
end
I'm looking to use some method like
#sum_a,#sum_b,#sum_c += [1,2,3]
My instant thought is: move the code to a model.
The objective should be "Thin Controllers", so they should not contain business logic.
Second, I like to present my report lines to my Views as OpenStruct() objects, which seems cleaner to me.
So I'd consider moving this accumulation logic into (most likely) a class method on Report and returning an array of "report line" OpenStructs and a single totals OpenStruct to pass to my View.
My controller code would become something like this:
#report_lines, #report_totals = Report.summarised_data_of_inv_and_dlvry_rpt(#part_or_service, #start_date, num_of_months)
EDIT: (A day later)
Looking at that adding accumulating-into-an-array thing, I came up with this:
require 'test/unit'
class Array
def add_corresponding(other)
each_index { |i| self[i] += other[i] }
end
end
class TestProblem < Test::Unit::TestCase
def test_add_corresponding
a = [1,2,3,4,5]
assert_equal [3,5,8,11,16], a.add_corresponding([2,3,5,7,11])
assert_equal [2,3,6,8,10], a.add_corresponding([-1,-2,-2,-3,-6])
end
end
Look: a test! It seems to work OK. There are no checks for differences in size between the two arrays, so there's lots of ways it could go wrong, but the concept seems sound enough. I'm considering trying something similar that would let me take an ActiveRecord resultset and accumulate it into an OpenStruct, which is what I tend to use in my reports...
Our new Array method might reduce the original code to something like this:
totals = [0,0,0,0,0,0,0]
date = #start_date
num_of_months.times do
wp, projected_wp, invoice_line, projected_il, line_item, opp = Report.data_of_invoicing_and_delivery_report(#part_or_service,date)
totals.add_corresponding [wp, projected_wp, invoice_line, projected_il, line_item, opp, invoice_line - line_item]
#report_lines << [date.strftime("%m/%Y"),wp,projected_wp ,invoice_line,projected_il,line_item,gross_profit,opp]
date = date.next_month
end
#sum_wp, #sum_projcted_wp, #sum_il, #sum_projcted_il, #sum_li, #sum_opportunities, #sum_gross_profit = totals
...which if Report#data_of_invoicing_and_delivery_report could also calculate gross_profit would reduce even further to:
num_of_months.times do
totals.add_corresponding(Report.data_of_invoicing_and_delivery_report(#part_or_service,date))
end
Completely un-tested, but that's a hell of a reduction for the addition of a one-line method to Array and performing a single extra subtraction in a model.
Create a summation object that contains all those fields, pass the entire array to #sum.increment_sums(Report.data_of...)

Rails, using time_select on a non active record model

I am trying to use a time_select to input a time into a model that will then perform some calculations.
the time_select helper prepares the params that is return so that it can be used in a multi-parameter assignment to an Active Record object.
Something like the following
Parameters: {"commit"=>"Calculate", "authenticity_token"=>"eQ/wixLHfrboPd/Ol5IkhQ4lENpt9vc4j0PcIw0Iy/M=", "calculator"=>{"time(2i)"=>"6", "time(3i)"=>"10", "time(4i)"=>"17", "time(5i)"=>"15", "time(1i)"=>"2009"}}
My question is, what is the best way to use this format in a non-active record model. Also on a side note. What is the meaning of the (5i), (4i) etc.? (Other than the obvious reason to distinguish the different time values, basically why it was named this way)
Thank you
You can create a method in the non active record model as follows
# This will return a Time object from provided hash
def parse_calculator_time(hash)
Time.parse("#{hash['time1i']}-#{hash['time2i']}-#{hash['time3i']} #{hash['time4i']}:#{hash['time5i']}")
end
You can then call the method from the controller action as follows
time_object = YourModel.parse_calculator_time(params[:calculator])
It may not be the best solution, but it is simple to use.
Cheers :)
The letter after the number stands for the type to which you wish it to be cast. In this case, integer. It could also be f for float or s for string.
I just did this myself and the easiest way that I could find was to basically copy/paste the Rails code into my base module (or abstract object).
I copied the following functions verbatim from ActiveRecord::Base
assign_multiparameter_attributes(pairs)
extract_callstack_for_multiparameter_attributes(pairs)
type_cast_attribute_value(multiparameter_name, value)
find_parameter_position(multiparameter_name)
I also have the following methods which call/use them:
def setup_parameters(params = {})
new_params = {}
multi_parameter_attributes = []
params.each do |k,v|
if k.to_s.include?("(")
multi_parameter_attributes << [ k.to_s, v ]
else
new_params[k.to_s] = v
end
end
new_params.merge(assign_multiparameter_attributes(multi_parameter_attributes))
end
# Very simplified version of the ActiveRecord::Base method that handles only dates/times
def execute_callstack_for_multiparameter_attributes(callstack)
attributes = {}
callstack.each do |name, values|
if values.empty?
send(name + '=', nil)
else
value = case values.size
when 2 then t = Time.new; Time.local(t.year, t.month, t.day, values[0], values[min], 0, 0)
when 5 then t = Time.time_with_datetime_fallback(:local, *values)
when 3 then Date.new(*values)
else nil
end
attributes[name.to_s] = value
end
end
attributes
end
If you find a better solution, please let me know :-)

Resources