Ruby on Rails 3: Associating extra data with a model - ruby-on-rails

Pretty much a total beginner to Ruby/Rails.
I have a Video with many Sections:
#video = Video.find(params[:id])
#sections=#video.sections;
I want to associate a colour attribute with each Section, but the colour is calculated in the controller, rather than stored in the database.
So far I have been simply creating a #colours array in my controller where the index matched up with the index of the section
hues = [41, 6, 189, 117, 279]
saturation = 100;
brightness = 45;
#colours = [];
#sections.each { #colours.push Color::HSL.new(hues[j%hues.length], saturation, brightness) }
so that #sections[i] corresponds to #colours[i].
This works fine, but doesn't seem like the best approach. I would like to extend my Sections model so that it has a 'colour' attribute, so that I could access it by doing #sections[i].colour
I tried putting this in models/sectiondata.rb :
class SectionData
extend Section
attr_accessor :colour
end
but when I try to do SectionData.new in my Controller I get an error saying it can't find the class. I also don't know how I would get the original #section to be part of the new SectionData class.
What is the best way to approach this problem? Any tips on my coding style would also be appreciated, Ruby is a big step away from what I'm used to.

I think its a better idea to implement hashes in this situation instead of having two arrays corresponding to each other.
For example,
result_hash = {sports_section => 'blue'}

First I tried putting them as attr_accessors in the Section model, don't do that. Bad coder! They will not show up if ActiveRecord queries the database again for that page load, even if the query is cached. If you need the ActiveRecord methods you can use ActiveModel
Instead, I created a class for the attribute accessors:
sectiondata.rb:
class SectionData
attr_accessor :left, :width, :timeOffset, :colour, :borderColour
end
Then you can work with them using an object which maps a Section object to SectionData:
xxx_controller.rb
#video = Video.find(params[:id])
#sections=#video.sections
require 'sectiondata.rb'
#sectionDatas=Hash.new
#sections.each do |i|
sd=SectionData.new
# set whatever attributes here, e.g.
sd.left= total/multiplier
#sectionDatas[i]=sd
end
Then, given a Section object called 'section', you could access it using #sectionDatas[section], and this will work for any database queries that occur in the page of

Related

How to map a model's integer attribute to a string?

I have a Hotels table in my database, and one of the columns is :status (integer). I'm looking to convert these integers into strings, so 1 = "Awaiting Contract", 2 = "Designing" and so on...
I have searched Stack for some answers, and the lack of them makes me think that I'm coming at this problem from the wrong angle? I used to do this in PHP whilst pulling the data. New-ish to Rails so any help, or best practise advice would be much appreciated.
Check enum of ActiveRecord - doc.
Here you can configure your :status:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def format_status
status.to_s.humanize
end
end
It'll create methods like this:
hotel.waiting_contract?
hotel.designing?
hotel.waiting_contract!
hotel.format_status # => "Waiting contract"
Hope that helps!
UPDATE
Similar functionality might be achieved by overriding the status method itself, although having separate methods is more advised:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def status
super.to_s.humanize
end
end
Furthermore, decorators are something you should look into for view-specific methods.
It depends what you need the list for. An alternative to the above ideas, is to create a hash. Hashes are very Ruby and designed just for this sort of paired data.
Create the hash, (enumeration typing is automatic.)
my_h = { "waiting" => 1, "design" => 2 }
Then to access
my_h["waiting"] = 1
There's much more you can do with hashes. This is just the simplest case.
A hash may or not fulfill your needs, but it's a splendid tool that comes with a nice set of Ruby worker methods.
http://ruby-doc.org/core-2.2.0/Hash.html

Intitalizing Object with Array of objects from another class Ruby

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

Fill array with nils for when nothing returned from query in ruby (RoR)

I have a model called foo with a date field.
On my index view, I am showing a typical "weekly view" for a specified week. To put the data in my view, I loop through each day of the specified week and query the data one day at time. I do this so that I can make sure to put a NIL on the correct day.
foos_controller.rb
for day in 0..6
foo = Foo.this_date(#date+day.days).first
#foos[day] = foo
end
index.html.haml
- for day in 0..6
%li
- if #foos[day].nil?
Create a new foo?
- else
Display a foo information here
Obviously, there's a lot of things wrong here.
I should find someone smart member to tell me how to write a good query so that I only have to do it once.
I should not have any if/else in my view
My goal here is to either show the content if the it is there for a particular day or show a "create new" link if not.
thanks for the help in advance!!
First, I have no idea what this_date actually does, but I'll assume it's retrieving a record with a specific date from your datastore. Instead of doing 7 queries, you can condense this into one using a date range:
Foo.where(date: (#date..(#date + 6.days)))
You can tack on a .group_by(&:date) to return something similar to the hash you are manually constructing, but using the actual dates as keys instead of the date offset.
To iterate over the dates in the view, I would recommend using Hash#fetch, which allows you to define a default return when a key is not present, e.g:
hash = { :a => 1, :b => 2 }
hash.fetch(:a){ Object.new } #=> 1
hash.fetch(:c){ Object.new } # #<Object:...>
The question now is what object to substitute for nil. If you want to avoid using conditionals here, I'd recommend going with the NullObject pattern (you could involve presenters as well but that might be a bit overkill for your situation). The idea here is that you would create a new class to substitute for a missing foo, and then simply define a method called to_partial_path on it that will tell Rails how to render it:
class NullFoo
def to_partial_path
"null_foos/null_foo"
end
end
You'll need to create partials at both app/views/foos/_foo.html.erb and app/views/null_foos/_null_foo.html.erb that define what to render in each case. Then, in your view, you can simply iterate thusly:
<% (#date..(#date + 6.days)).each do |date| %>
<%= render #foos.fetch(date){ NullDate.new } %>
<% end %>
Is this appropriate for your situation? Maybe it's also a bit overkill, but in general, I think it's a good idea to get in the habit of avoid nil checks whenever possible. Another benefit of the NullObject is that you can hang all sorts of behavior on it that handle these situations all throughout your app.

rails "where" statement: How do i ignore blank params

I am pretty new to Rails and I have a feeling I'm approaching this from the wrong angle but here it goes... I have a list page that displays vehicles and i am trying to add filter functionality where the user can filter the results by vehicle_size, manufacturer and/or payment_options.
Using three select form fields the user can set the values of :vehicle_size, :manufacturer and/or :payment_options parameters and submit these values to the controller where i'm using a
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, :vehicle_size => params[:vehicle_size] )
kind of query. this works fine for individual params (the above returns results for the correct vehicle size) but I want to be able to pass in all 3 params without getting no results if one of the parameters is left blank..
Is there a way of doing this without going through the process of writing if statements that define different where statements depending on what params are set? This could become very tedious if I add more filter options.. perhaps some sort of inline if has_key solution to the effect of:
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, if(params.has_key?(:vehicle_size):vehicle_size => params[:vehicle_size], end if(params.has_key?(:manufacturer):manufacturer => params[:manufacturer] end )
You can do:
#vehicles = Vehicle.order('vehicles.id ASC')
if params[:vehicle_size].present?
#vehicles = #vehicles.where(vehicle_size: params[:vehicle_size])
end
Or, you can create scope in your model:
scope :vehicle_size, ->(vehicle_size) { where(vehicle_size: vehicle_size) if vehicle_size.present? }
Or, according to this answer, you can create class method:
def self.vehicle_size(vehicle_size)
if vehicle_size.present?
where(vehicle_size: vehicle_size)
else
scoped # `all` if you use Rails 4
end
end
You call both scope and class method in your controller with, for example:
#vehicles = Vehicle.order('vehicles.id ASC').vehicle_size(params[:vehicle_size])
You can do same thing with remaining parameters respectively.
The has_scope gem applies scope methods to your search queries, and by default it ignores when parameters are empty, it might be worth checking

How to represent dynamically derived data? (model without a table?)

I have tables for salespeople, products, and sales_activities (consider these to be 'transactions', but Rails reserves that name, so I'm calling them sales_activities).
For each salesperson, I need to dynamically derive their sales_total for a given day.
To do this, I run through the list of sales_activities, and create my derived content list (as an array of objects that hold salesperson_id & sales_total). I then want to display it in a view somewhat equivalent to an 'index' view of salespeople, but this view does not correspond to any of the existing index views I already have, due to the extra field (sales_total).
My question is how do I best define the class (or whatever) for each instance of my dynamically derived data (salesperson_id + sales_total)? It seems I could use a model without a table (with columns salesperson_id and the derived sales_total). That way, I could build an array of instances of these types as I generate the dynamic content, and then hand off the resulting array to the corresponding index view. However, from reading around, this doesn't seem 'the Rails way'.
I'd really appreciate advice on how to tackle this. The examples I've seen only show cases where a single overall total is required in the index view, and not dynamic content per row that can't be derived by a simple 'sum' or equivalent.
[This is a simplified explanation of the actual problem I'm trying to solve, so I'd appreciate help with the 'dynamically derived view / model without table' problem, rather than a short-cut answer to the simplified problem outlined above, thanks]
Maybe a plain Ruby class would do the trick?
class SalesStats
def initialize(sales_person, date_range = Date.today)
#sales_person = sales_person
#date_range = date_range
end
def results
# return a array or hash (anything which responds to an 'each' method), e.g:
SalesActivity.find(:all, :conditions => { :sales_person => #sales_person, :created_at => #date_range }).group_by(&:sales_person).collect { |person, sales_activities| { :person => person, :total => sales_activities.sum(&:price) } }
end
end
in the view:
<% #sales_stats.results.each do | stat | %>
<%= stat[:person] %> - <%= stat[:total] %>
<% end %>
However like mischa said in the comments this could equally be achieved using a method on SalePerson:
class SalesPerson < AR::Base
has_many :sales_activities
def total_sales(date_range)
sales_activities.find(:all, :conditions => { :created_at => date_range }).collect { ... }
end
end
Note: date_range can be a single date or a range e.g (Date.today-7.days)..Date.today

Resources