Instance Variables in Rails Model - ruby-on-rails

I want to initialize an instance variable within my Rails model that will hold an array and I want to access this variable in other methods within my model. I tried this:
class Participant < ActiveRecord::Base
#possible_statuses = [
'exists',
'paired',
'quiz_finished',
'quiz_results_seen',
'money_sent'
]
def statuses
#possible_statuses
end
But when I tried the following using rails console:
Participant.first.statuses
I am returned nil :(
Why does this happen?
Is there a way to accomplish what I am trying to accomplish?

I would recommend using a constant for this kind of cases:
class Participant < ActiveRecord::Base
STATUSES = [
'exists',
'paired',
'quiz_finished',
'quiz_results_seen',
'money_sent'
]
If you want to access that array from the inside class, just do STATUSES, and from the outside class use Participant::STATUSES

In your example, #possible_statuses is a variable on the class rather than on each instance of the object. Here is a rather verbose example of how you might accomplish this:
class Participant < ActiveRecord::Base
#possible_statuses = [
'exists',
'paired',
'quiz_finished',
'quiz_results_seen',
'money_sent'
]
def self.possible_statuses
#possible_statuses
end
def possible_statuses
self.class.possible_statuses
end
def statuses
possible_statuses
end
end
As mentioned in other answers, if that list of statuses never changes, you should use a constant rather than a variable.

The best answer for me was to create a class variable, not an instance variable:
##possible_statuses = [
'exists',
'paired',
'chat1_ready',
'chat1_complete'
]
I could then freely access it in methods of the class:
def status=(new_status)
self.status_data = ##possible_statuses.index(new_status)
end

The instance variable will only exist when you create the model, since it is not being stored in a database anywhere. You will want to make it a constant as per Nobita's suggestion. Alternatively, you could make it a module like...
module Status
EXISTS = "exists"
PAIRED = "paired"
QUIZ_FINISHED = "quiz_finished"
QUIZ_RESULTS_SEEN = "quiz_results_seen"
MONEY_SENT = "money_sent"
def self.all
[EXISTS, PAIRED, QUIZ_FINISHED, QUIZ_RESULTS_SEEN, MONEY_SENT]
end
end
This gives you a bit more control and flexibility, IMO. You would include this code nested in your model.

Related

updating class variables in rails

I have a class variable named ##customers which I would like to continually update via a method.
I initialize the variable (as an empty array) at the top of my model. And then update it when the method update_customers is called:
class Customer
##customers = []
def update_customers(new_customer)
##customers << new_customer
end
end
I am concerned about ##customers being re-initialized to [] and losing the data. Could such re-initialization occur? When would it happen?
Nope; ##customers will not be re-initialized to [] when update_customers is called from a new Customer object. That is how class variables work.
See http://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/45-more-classes/lessons/113-class-variables for an in-depth treatment of class variables.
As mentioned there:
There aren't very many cases that you would need to use class variables.`
class Customer
##customers = []
def self.update_customers(new_customer)
##customers << new_customer
end
end
Class variables are static. They are initialized just once.
It would be good if you declared update_customers as class method
You could call the method like
cust1 = Customer.new
Customer.update_customers cust1

What is the proper way to access class variables in Ruby 1.9?

I'm trying to set some class variables to store paths in a Rails application (but I think this more a ruby question)
Basically my class looks like this
class Image < ActiveRecord::Base
##path_to_folder = "app/assets"
##images_folder = "upimages"
##path_to_images = File.join(##path_to_folder, ##images_folder)
end
But when I try to access ##path_to_images from my controller by doing Image.path_to_images, I get a NoMethodError
When I try with Image.class_eval( ##path_to_images ), I get uninitialized class variable ##path_to_images in ImagesController
I've searched around and all I've seen says those would work, so I'm very confused about this
What's more, I tried defining simple classes with the ruby console like so
class Bidule
##foo = "foo"
Bar = "bar"
end
And so I tried, I think, all the ways possible (previous 2 included) to access them but no way I always get an exception raised
Rails provides class level attribute accessor for this functionality
Try
class Image < ActiveRecord::Base
cattr_accessor :path_to_folder
##path_to_folder = "app/assets"
end
Then to access path_to_folder class variable just use
Image.path_to_folder
But people always suggest to avoid class variables due to its behavior in inheritance.So you can use constants like
class Image < ActiveRecord::Base
PATH_TO_FOLDER = "app/assets"
end
Then you can access the constant like
Image::PATH_TO_FOLDER
Although I wouldn't in general recommend it, you can access class variables by passing a string to class_eval as in:
Image.class_eval('##path_to_folder')
at least in later versions of Ruby.
Note, however, that class variables are associated with the uppermost class in a subclassed hierarchy. In the case of ActiveRecord subclasses like this one, this means that these class variables really exist in the namespace of ActiveRecord::Base.
If you can't or don't want to extend class use:
Image.class_variable_get(:##path_to_images)
Best way is to set and get the value using methods. Below is a sample code
class Planet
##planets_count = 0
def initialize(name)
#name = name
##planets_count += 1
end
def self.planets_count
##planets_count
end
def self.add_planet
##planets_count += 1
end
def add_planet_from_obj
##planets_count += 1
end
end
Planet.new("uranus")
Plant.add_planet
obj = Planet.new("earth")
obj.add_planet_from_obj
Class variables are rarely used in Ruby applications because they have a lot of limitations and also tend to run against the grain of proper Object-Oriented design.
In nearly every case a class variable can be replaced with a proper constant, a class method, or both.
Your example is probably better described as:
class Image < ActiveRecord::Base
PATH_TO_FOLDER = "app/assets"
IMAGES_FOLDER = "upimages"
PATH_TO_IMAGES = File.join(PATH_TO_FOLDER, IMAGES_FOLDER)
end
Class variables are private to the class in question and don't trickle down to sub-classes and are difficult to access from an external context. Using constants allows the use of things like:
image_path = Image::PATH_TO_FOLDER
There are some circumstances under which a class variable is more reasonable than the alternative, but these are usually very rare.
You can do that by wrapping it in a class method, like this:
def self.path_to_images
##path_to_images
end
but I should mention that you should try to avoid using class variables in rails
I would modify it like this:
class Image < ActiveRecord::Base
##path_to_folder = "app/assets"
##images_folder = "upimages"
##path_to_images = File.join(##path_to_folder, ##images_folder)
def initialize
end
...
def self.path_to_folder
##path_to_folder
end
end
What you've done here is make the class variable into a method, so you can now access is using a .method name call. Since this is a class variable though, you can only call this on the class itself, not on the instance of a class.
You are getting a 'NoMethodError' because you're calling the class variable using a method which has not exist. The code above defines this method on the lines where you say:
def self.path_to_folder
##path_to_folder
end
This will now work:
Image.path_to_folder

Instance Variables on Active Record Models

I've created an instance variable on an ActiveRecord Model where I want to save a bit of computationally heavy data in each instance... Here's my code to do that:
class Account < ActiveRecord::Base
after_initialize :init
attr_accessor :market_value
def init
self.market_value ||= my_lengthy_function
end
end
where I'll take the hit to get that instance data (market_value) run when I init an instance of the model.
This works - I can see how I don't have to re-calculate my market_value property.
My problem is, when I access that object through another context, rails doesn't leverage that data as I'd expect.
For example:
Say I create an instance of an account (a = Account.find_by_id(2)). That market_value will be calculated on that object once.
If I have a nested has_many relationship to something called "holdings" (not in my sample code) on that account object, I'm going to want each of those holding objects (a holding) to be able to use it's parent account object.
However, in my code, I access the account from it's nested holding objects (my_holding.account.market_value) - I re-instantiate an instance of that account object, and incur that costly computation, even though it's already been computed.
How can I better leverage that account market_value property so that it doesn't keep recalculating?
I would not put the calculation logic in the ActiveRecord model. Maybe something along these lines:
class MarketValueCalculator
def initialize()
#market_values = {}
end
def calculate_for_account(account)
#market_values[account.id] ||= heavy_lifting
end
def heavy_lifting
###
end
end
#calculator = MarketValueCalculator.new
#market_value = #calculator.calculate_for_account(account)
#market_value = #calculator.calculate_for_account(my_holding.account)
i would build up a simple cache on class-level with the model ids as keys:
class Account < ActiveRecord::Base
def market_value
##market_value ||= {}
##market_value[id] ||= my_lengthy_function
end
end
not tested if this would work though, especially with class reloading in development.

Ruby/Rails: Is it possible to execute a default method when calling an instance (#instance == #instance.all IF "all" is the default method)?

I understand my question is a bit vague but I don't know how else to describe it. I've asked in numerous places and no one seems to understand why I want to do this. But please bear with me, and I'll explain why I want something like this.
I'm using Liquid Templates to allow users to make some dynamic pages on my site. And for those that don't know, Liquid uses a class of theirs called LiquidDrop to expose certain items to the user. Any method in the drop can be called by the Liquid template.
class PageDrop < Liquid::Drop
def initialize(page)
#page = page
end
def name
#page.name
end
def children
PagesDrop.new(#page.children)
end
end
class PagesDrop < Liquid::Drop
def initialize(pages)
#pages = pages
end
def group_by
GroupByDrop.new(#pages)
end
def all
#pages.all
end
def size
#pages.size
end
end
For example, I want to be able to do this:
#page_drop = PageDrop.new(#page)
#page_drop.children # to get an array of children
instead of
#page_drop.children.all
Why do I have a pages drop?
Because I want to be able to cleanly split up the methods I can do to an array of pages, and methods I can do to a single page. This allows me to group pages like so:
#page_drop.children.group_by.some_method_here_that_the_group_drop_contains
To make it simpler for my users, I don't want them to have to think about adding "all" or not to a drop instance to get the "default" object/s that it contains. To reiterate:
#pages_drop = PagesDrop.new(Page.all)
#pages_drop == #pages_drop.pages #I want this to be true, as well as
#pages_drop == #pages_drop.all
Where did I get this idea?
In Rails, a scope (association object) (#person.friends) seems to return the array when you do certain things to it: #person.friends.each, for person in #person.friends
This isn't really possible. When you write #instance you aren't really calling an instance as you describe, you're getting a reference to the object that #instance refers to.
The reason it seems to work with the collections for Rails' associations is that the the association objects are instances of Array that have had some of their methods overridden.
I would consider removing PagesDrop and using the group_by(&:method) syntax if you want a concise way to express groupings. If you do want to keep it then you can get some way towards what you want by implementing each and [] on PagesDrop and having them delegate to #pages. That will let you use #page_drop.children in for loops, for instance.
It looks like you want to implement has_many outside of rails. Will the following work?
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = []
end
def name
#page.name
end
end
This allows you to do the following:
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
This also gives you all the standard array functions (group_by, size, each, etc). If you want to add your own methods, create a class that inherits from Array and add your methods there.
class PageArray < Array
def my_method
self.each{|a| puts a}
end
end
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = PageArray.new
end
[...]
end
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
#page_drop.children.my_method # Prints all the children
Then any functions you don't define in PageArray fall through to the Ruby Array methods.

Dynamically instantiate Rails nested STI subclass?

Let's say I have a class like:
class Basket < ActiveRecord::Base
has_many :fruits
Where "fruits" is an STI base class having subclasses like "apples", "oranges", etc...
I'd like to be able to have a setter method in Basket like:
def fruits=(params)
unless params.nil?
params.each_pair do |fruit_type, fruit_data|
fruit_type.build(fruit_data)
end
end
end
But, obviously, I get an exception like:
NoMethodError (undefined method `build' for "apples":String)
A workaround I thought of works like this:
def fruits=(params)
unless params.nil?
params.each_pair do |fruit_type, fruit_data|
"#{fruit_type}".create(fruit_data.merge({:basket_id => self.id}))
end
end
end
But that causes the Fruit STI object to be instantiated before the Basket class, and so the basket_id key is never saved in the Fruit subclass (because basket_id doesn't exist yet).
I'm totally stumped. Anyone have any ideas?
Instead of adding a setter method in Basket, add it in Fruit:
class Fruit < ActiveRecord::Base
def type_setter=(type_name)
self[:type]=type_name
end
end
Now you can pass the type in when you build the object through an association:
b = Basket.new
b.fruits.build(:type_setter=>"Apple")
Note that you can't assign :type this way, since it is protected from mass assignment.
EDIT
Oh, you wanted to run different callbacks depending on the subclass? Right.
You could do this:
fruit_type = "apples"
b = Basket.new
new_fruit = b.fruits << fruit_type.titleize.singularize.constantize.new
new_fruit.class # Apple
or define a has_many association for each type:
require_dependency 'fruit' # assuming Apple is defined in app/models/fruit.rb
class Basket
has_many :apples
end
then
fruit_type = "apples"
b = Basket.new
new_fruit = b.send(fruit_type).build
new_fruit.class # Apple
In Ruby terms, "#{x}" is simply equivalent to x.to_s which for String values is exactly the same as the string itself. In other languages, like PHP, you can de-reference a string and treat it as a class, but that's not the case here. What you probably mean is this:
fruit_class = fruit_type.titleize.singularize.constantize
fruit_class.create(...)
The constantize method converts from a string to the equivalent class, but it is case sensitive.
Keep in mind that you're exposing yourself to the possibility someone might create something with fruit_type set to "users" and then go ahead and make an administrator account. What's perhaps more responsible is to do an additional check that what you're making is actually of the right class.
fruit_class = fruit_type.titleize.singularize.constantize
if (fruit_class.superclass == Fruit)
fruit_class.create(...)
else
render(:text => "What you're doing is fruitless.")
end
One thing to watch out for when loading classes this way is that constantize will not auto-load classes like having them spelled out in your application does. In development mode you may be unable to create subclasses that have not been explicitly referenced. You can avoid this by using a mapping table which solves the potential security problem and pre-loading all at once:
fruit_class = Fruit::SUBCLASS_FOR[fruit_type]
You can define this constant like this:
class Fruit < ActiveRecord::Base
SUBCLASS_FOR = {
'apples' => Apple,
'bananas' => Banana,
# ...
'zuchini' => Zuchini
}
end
Using the literal class constant in your model will have the effect of loading them immediately.

Resources