Relationships - accessing children elements - ruby-on-rails

I'm trying to add a simple inventory management system. A class product has_many variants, and the variants belong_to a product and therefore have a product_id, along with a name and a quantity. When the user creates the product, I have the product generate 11 different variants (just with numerical values) by calling the following
Located in variants.rb (model)
def self.create_multiple_variants( product_id )
p = Product.find(product_id)
i = 11
while i <= 21
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
i += 1
end
end
Then when the user tries to show the page, the program will go through each variant belonging to the product and see if their is any quantity (which the admin adjusts along the way) like so:
Located in the view:
<div class="size"><br/>Size: <%= f.select(:size, #sizes_availiable, :prompt => "Select a Size...")
Located in product_controller:
#sizes_availiable = Variants.create_inventory_array( #product.id )
Located in variants.rb (model)
def self.create_inventory_array( product_id )
p = Product.find(product_id)
a = []
p.variants.each do |v|
a << variant.name if variant.qty > 0
end
a
end
I know with the naming it is a little confusing, as I am setting it up as something bigger but deprecating it for now, so sorry of thats a little confusing. For now you can think of variant as "size"
But the creation portion of it works fine, however when I go to show the product I get this message:
NameError in ProductController#show
app/models/variants.rb:20:in create_inventory_array'
app/controllers/product_controller.rb:18:inshow'
I assume that the way I am building the relationship is the source of the problem, either that or how I am calling it. Any ideas?
UPDATE:
I used the suggestions below, and it seems that now the problem lies in the second function. Here is my new variants.rb and the error I get:
class Variants < ActiveRecord::Base
attr_accessible :product_id, :name, :qty
belongs_to :product
def self.create_multiple_variants( product_id )
p = Product.find(product_id)
for i in 11..21
v = Variants.create
v.product = p
v.name = (i*2)
v.qty = 0
v.save!
end
end
def self.create_inventory_array( product_id )
p = Product.find(product_id)
a = []
p.variants.each do |variant|
a << variant.name if variant.qty > 0
end
a
end
end
NoMethodError in ProductController#create
undefined method `Variants' for #<Product:0x007fe9561ad550>
Application Trace | Framework Trace | Full Trace
app/models/variants.rb:8:in `block in create_multiple_variants'
app/models/variants.rb:7:in `each' app/models/variants.rb:7:in
`create_multiple_variants' app/controllers/product_controller.rb:33:in
`create
I still believe it's an issue with how the relationship is being build (I'm assigning variants.product = current_product, yet I call product.variants - I feel like the relationship is not being built both ways)

The problem is in this code:
p.variants.each do |v|
a << variant.name if variant.qty > 0
end
you pass in the variable v but refer to it as variant. To fix it change the line to
p.variants.each do |variant|
Also read this: http://guides.rubyonrails.org/active_record_querying.html#conditions you could make the code a lot more elegant by querying the variants for desired product_id and qty, and then calling map to get the names only.
Also this can be improved:
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
to
new_variant = p.variants.create name: "#{i*2}", qty: 0

Yes. You need to save the object.
To save it at the end of your loop:
new_variant.save!
Sidenote about this loop:
i = 11
while i <= 21
...
i += 1
end
This is a better way to write it because it's clearer:
for i in 11..21 do
...
end
And for blocks like this:
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
new_variant.save!
Make it easier to read:
v = Variants.create
v.product = p
v.name = i*2
v.qty = 0
v.save!

I figured out what was wrong - my model is Variants.rb (with an s) which at some point caused a problem. I renamed the file variants.rb as well as the class name Variants to variant.rb and Variant respectivly, restarted the server, and it worked! Thanks to those who helped!

Related

Wrong return value when calling a method

I need to Add a tractor_beam instance method that takes a string description of an item as a parameter (e.g., "cow"). When called, the method should disable the shield, add the item to the inventory along with the ship's current location if it isn't too heavy to pick up (see algorithm below), enable the shield again, and return true. If the item is too heavy to pick up, the method should skip the inventory update and return false.
Algorithm:
An item is too heavy to pick up if its letters add up to more than 500. using .ord (Not very scientific, i know.) For example, the letters of cow add up to 329, so our tractor beam can abduct a cow, no problem.
My problem is that it returns nil and an empty hash, how do i break down the item to add each together?
Code:
class Spaceship
attr_accessor :name, :location, :item, :inventory
attr_reader :max_speed
def initialize (name, max_speed, location)
puts "Initializing new Spaceship"
#name = name
#max_speed = max_speed
#location = location
#item = item
#inventory = {}
end
def disable_shield
puts "Shield is off!"
end
def enable_shield
puts "Shield is on!"
end
def warp_to(location)
puts "Traveling at #{max_speed} to #{location}!"
#location = location
end
def tractor_beam(item)
disable_shield
item = item.split('')
item.each do |let|
let.ord
let + let.next
end
return item
if item > 500
enable_shield
#inventory[#location] = item
return true
else
return false
end
end
end
Driver Code:
uss_enterprise = Spaceship.new("USS Enterprise","200,000 mph", "China")
hms_anfromeda = Spaceship.new("HMS Andromeda", "108,277 mph", "China")
uss_enterprise.disable_shield
hms_anfromeda.enable_shield
p hms_anfromeda.location
hms_anfromeda.warp_to("Namibia")
p hms_anfromeda.location
hms_anfromeda.tractor_beam("cow")
p hms_anfromeda.item
Terminal:
Initializing new Spaceship
Initializing new Spaceship
Shield is off!
Shield is on!
"China"
Traveling at 108,277 mph to Namibia!
"Namibia"
Shield is off!
nil
Firstly, you have a return statement before your if conditional, so the conditional will never be ran. Remove that.
Secondly, you get the weight of the item by using ord, but you aren't assigning the value to anything:
item.each do |let|
let.ord
let + let.next
end
return item
if item > 500
This should do the trick:
item = item.split('')
weight = 0
item.each do |let|
weight += let.ord # add the ord of this letter to the weight
end
if weight > 500 # weight is now the ord of each letter of item 'cow'
enable_shield
#inventory[#location] = item
return true
else
return false
end
This line return item in your tractor_beam method will get run every time before getting to your if statement I think that is causing the problem.
Also you are not using the instance variable #item that you are created in the initialize method I think you might actually want something like this:
def tractor_beam(item)
disable_shield
#item = item.split('')
weight = 0
#item.each do |let|
weight += let.ord
end
if weight < 500
enable_shield
#inventory[#location] = #item
return true
else
return false
end
end
end

How can I iterate through a model then iterate again in my view?

I want to pull data for each of my users. I grab their person_id from my user table, then use each person's ID to figure out how many days each person has available, and show that in my view.
I'm not sure if I am doing this correctly because I am iterating in my controller then again in my view.
def how_many_days_users_have
#my_group = User.all.pluck(:person_id)
#my_group.each do |v|
#indirect_id_v = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:a_code).first
#v_range = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:ac).first
#v_range_taken = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:taken).first
#total_v_hours = #v_range.to_d - #v_range_taken.to_d
#total_v_days = #total_v_hours / 8
end
Then in my view I use this to show me this data:
%tr.trace-table
-#indirect_id_v.each do |idd|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= idd
-#total_v_days.each do |days|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= days
Okay, first things first, move some of that junk to your model, like so:
class Empaccrl < ActiveRecord::Base
def self.all_people
where(person_id: User.all.pluck(:person_id))
end
def self.active_people
all_people.where(is_active: 'Y')
end
def self.active_vacation_data
active_people.select(:person_id, :ac, :taken)
end
def total_v_hours
ac.to_d - taken.to_d
end
def total_v_days
total_v_hours / 8
end
end
Then you can use:
peoples_vacation_information = Empaccrl.active_vacation_data.all
peoples_vacation_information.map do |person|
p "person #{person.person_id} has #{person.total_v_days} vacation days"
end
Honestly, you don't even need all that, but I'm not sure why you are doing what you are doing, so I figured better be safe and add stuff. Whatever you don't need, just ignore.

contextual binding in rails

Normally i do like this
for loops
# app/view/products/index.html.haml
- #products.each do |product|
= product.name
= product.foo
= product.bar
normal scoping
# app/view/products/show.html.haml
= #product.name
= #product.price
= #product.xyz
See in above situation, i am repeating myself. I am using same product word every time. I want it something like which could attach/bind the method as per my context.
I rather prefer to do something like this
For loops i like do something like
- #products.each(context_binding: true) do
= name
= foo
= bar
for scoping
- context_binding #product do
= name
= price
= xyz
I guess that is possible and can be done with method missing i dont know how to do it. Can you give some hints so i can archive such type of things.
Just to give you an idea:
class With
def initialize(obj)
#obj = obj
#str = ''
end
def method_missing(name, *args, &block)
#str += #obj.send(name, *args, &block).to_s
end
end
def with(obj, &block)
With.new(obj).instance_eval(&block).to_s
end
Product = Struct.new(:name, :price)
product = Product.new('apple', '2')
output = with(product) do
name
price
end
puts output

Nested ActiveRecords: Find many childrens of many parents

In my Rails 3.2 app a Connector has_many Incidents.
To get all incidents of a certain connector I can do this:
(In console)
c = Connector.find(1) # c.class is Connector(id: integer, name: string, ...
i = c.incidents.all # all good, lists incidents of c
But how can I get all incidents of many connectors?
c = Connector.find(1,2) # works fine, but c.class is Array
i = c.incidents.all #=> NoMethodError: undefined method `incidents' for #<Array:0x4cc15e0>
Should be easy! But I don't get it!
Here’s the complete code in my statistics_controller.rb
class StatisticsController < ApplicationController
def index
#connectors = Connector.scoped
if params['connector_tokens']
logger.debug "Following tokens are given: #{params['connector_tokens']}"
#connectors = #connectors.find_all_by_name(params[:connector_tokens].split(','))
end
#start_at = params[:start_at] || 4.weeks.ago.beginning_of_week
#end_at = params[:end_at] || Time.now
##time_line_data = Incident.time_line_data( #start_at, #end_at, 10) #=> That works, but doesn’t limit the result to given connectors
#time_line_data = #connectors.incidents.time_line_data( #start_at, #end_at, 10) #=> undefined method `incidents' for #<ActiveRecord::Relation:0x3f643c8>
respond_to do |format|
format.html # index.html.haml
end
end
end
Edit with reference to first 3 answers below:
Great! With code below I get an array with all incidents of given connectors.
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten
But idealy I'd like to get an Active Records object instead of the array, because I'd like to call where() on it as you can see in methode time_line_data below.
I could reach my goal with the array, but I would need to change the whole strategy...
This is my time_line_data() in Incidents Model models/incidents.rb
def self.time_line_data(start_at = 8.weeks.ago, end_at = Time.now, lim = 10)
total = {}
rickshaw = []
arr = []
inc = where(created_at: start_at.to_time.beginning_of_day..end_at.to_time.end_of_day)
# create a hash, number of incidents per day, with day as key
inc.each do |i|
if total[i.created_at.to_date].to_i > 0
total[i.created_at.to_date] += 1
else
total[i.created_at.to_date] = 1
end
end
# create a hash with all days in given timeframe, number of incidents per day, date as key and 0 as value if no incident is in database for this day
(start_at.to_date..end_at.to_date).each do |date|
js_timestamp = date.to_time.to_i
if total[date].to_i > 0
arr.push([js_timestamp, total[date]])
rickshaw.push({x: js_timestamp, y: total[date]})
else
arr.push([js_timestamp, 0])
rickshaw.push({x: js_timestamp, y: 0})
end
end
{ :start_at => start_at,
:end_at => end_at,
:series => rickshaw #arr
}
end
As you only seem to be interested in the time line data you can further expand the map examples given before e.g.:
#time_line_data = #connectors.map do |connector|
connector.incidents.map do |incident|
incident.time_line_data(#start_at, #end_at, 10)
end
end
This will map/collect all the return values of the time_line_data method call on all the incidents in the collection of connectors.
Ref:- map
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten

Searching and comparing ActiveRecord attributes to find largest value

I have a model that would look something like:
my_diet = Diet.new
my_diet.food_type_1 = "beef"
my_diet.food_type_1_percentage = 40
my_diet.food_type_2 = "carrots"
my_diet.food_type_2_percentage = 50
my_diet.food_type_3 = "beans"
my_diet.food_type_3_percentage = 5
my_diet.food_type_4 = "chicken"
my_diet.food_type_4_percentage = 5
I need to find which food_type has the highest percentage. So far I've tried creating a hash out of the attibutes and percentages then sorting the hash (see below) but it feels like there must be a cleaner way to do it.
food_type_percentages = { :food_type_1 => my_diet.foo_type_percentage_1_percentage.nil? ? 0 : my_dient.food_type_1_percentage,
:food_type_2 => my_diet.foo_type_percentage_2_percentage.nil? ? 0 : my_dient.food_type_2_percentage,
:food_type_3 => my_diet.foo_type_percentage_3_percentage.nil? ? 0 : my_dient.food_type_3_percentage,
:food_type_4 => my_diet.foo_type_percentage_4_percentage.nil? ? 0 : my_dient.food_type_4_percentage
}
food_type_percentages.sort {|a,b| a[1]<=>b[1]}.last
Any ideas?
Thanks!
To find the max value amongst columns of an existent row in the DB, do the following:
d = Diet.first(:select => "*, GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage) AS top_food_type_percentage,
CASE GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage)
WHEN food_type_1_percentage THEN food_type_1
WHEN food_type_2_percentage THEN food_type_2
WHEN food_type_3_percentage THEN food_type_3
WHEN food_type_4_percentage THEN food_type_4
END AS top_food_type")
d.top_food_type # carrots
d.top_food_type_percentage # 50
If you are trying to find the top food type in the current model instance then
class Diet < ActiveRecord::Base
def top_food_type
send(top_food_type_col)
end
def top_food_type_percentage
send("#{top_food_type_col}_percentage")
end
FOOD_TYPE_COL = %w(food_type_1 food_type_2 food_type_3 food_type_4)
def top_food_type_col
#top_food_type_col ||= FOOD_TYPE_COL.sort do |a, b|
send("#{a}_percentage") <=> send("#{b}_percentage")
end.last
end
end
Now you can do the following:
d = Diet.new
....
....
....
d.top_food_type # carrots
d.top_food_type_percentage # 50
I assume food_percentage is the column
if you just want to find out ref this
Diet.maximum('food_percentage') # gives 50
OR you want complete record use this
Diet.find(:first, :order=> 'food_percentage DESC', :limit=>1)

Resources