ruby hash within hash and a singleton method- cant access instance variable - ruby

#!/usr/bin/env ruby
# this is the data I have
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class Student
# not fully implemented - this is what I need help on.
def get_id_original
# I need this to return the value #data[:student][:id]
end
def get_city_original
# I need this to return the value #data[:contact_info][:city]
end
end
s = Student.new
# this is the original method
# how can I access the #data variable here I tried #data[:student][:id] doesnt work
# I realize that data is outside of the scope of this method. However, is there any way!
s.get_id_original
# My goal is to have a singleton method that acts exactly like get_id_original,
# but get_id_original doesn't work.
def s.id
get_id_original
end

It can be done!
It didn't at first work because #data is an instance attribute of the top level object, so even though Student is derived from Object the attribute isn't in the new instance.
But you can pass self into s.id, and so then the only thing you need to add is an accessor for the data attribute.
However, that's slightly tricky because attr_reader et al are private class methods so you can't use them directly, and you can't (because it's private) just say self.class.attr_reader, you have to open up Object and do it...with these changes your program works...
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
def s.id o
o.data[:student][:id]
#how can I access the #data variable here I tried #data[:student][:id] doesnt work
#I realize that data is outside of the scope of this method. However, is there any way!
end
class Object
attr_reader :data
end
puts s.id self

First off, your id method actually has to go into the class.
You could try something like this:
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :id
def initialize(student)
self.id = student[:id]
end
end
s = Student.new(#data[:student])
puts s.id

#!/usr/bin/ruby
#data = { :student => { :id => '123477', :first_name => 'Lazlo', :last_name =>'Fortunatus', :email=>'Lazlo#fortunatus.org' }, :contact_info => { :telephone=>'1 415 222-2222', :address => '123 Main St', :city =>'Beverly Hills', :state=>'California', :zip_code=>90210, :social_security_number =>'111-11-1111' } }
class Student
def initialize( data )
#data = data
end
def get_id_override
#data[:student][:id]
end
def get_first_name_override
#data[:student][:first_name]
end
def get_last_name_override
#data[:student][:last_name]
end
def get_email_override
#data[:student][:email]
end
def get_telephone_override
#data[:contact_info][:telephone]
end
def get_city_override
#data[:contact_info][:city]
end
def get_state_override
#data[:contact_info][:state]
end
def get_zip_code_override
#data[:contact_info][:zip_code]
end
def get_social_security_number_override
#data[:contact_info][:social_security_number]
end
end
s = Student.new(#data)
def s.id
get_id_override
end
def s.first_name
get_first_name_override
end
def s.last_name
get_last_name_override
end
def s.email
get_email_override
end
def s.contact_info
get_telephone_override
end
def s.city
get_city_override
end
def s.state
get_state_override
end
def s.zipcode
get_zip_code_override
end
def s.ssn
get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
Here is the answer after some work. Anyone has a better answer than mine let me know.

You really should be passing in the data object so it s has its own reference to it.
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :data
def initialize(data)
#data = data
end
end
s = Student.new(#data)
# or you can use s.data = #data
def s.id
#data[:student][:id]
end
puts s.id
A word of caution. Any modifications to #data in the outermost scope will be reflected in s, because both #data variables point to the same object in memory.
But what if you don't want to modify the Student class? You can just add the accessor to s:
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
class << s
attr_accessor :data
end
def s.id
#data[:student][:id]
end
s.data = #data
puts s.id

This code does the equivalent of your own answer, with some improvements. (Only by reading that did I realize what you were trying to accomplish.) To avoid being overly complex, I tried to avoid dynamically generating method names.
#!/usr/bin/env ruby
require 'forwardable'
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class ContactInfo
def initialize( data )
#data = data
end
def get_telephone_override
#data[:telephone]
end
def get_city_override
#data[:city]
end
def get_state_override
#data[:state]
end
def get_zip_code_override
#data[:zip_code]
end
def get_social_security_number_override
#data[:social_security_number]
end
end
class Student
extend Forwardable # enables delegation (see ruby-doc.org's standard library)
# delegates multiple methods to #contact_info, so they can be called on Student.
# Remember to have the leading colon.
def_delegators :#contact_info,
:get_telephone_override,
:get_city_override,
:get_state_override,
:get_zip_code_override,
:get_social_security_number_override
def initialize( data )
#data = data[:student]
# this is an example of composing objects to achieve separation of concerns.
# we use delegators so ContactInfo methods are available on the Student instance.
#contact_info = ContactInfo.new(data[:contact_info])
end
def get_id_override
#data[:id]
end
def get_first_name_override
#data[:first_name]
end
def get_last_name_override
#data[:last_name]
end
def get_email_override
#data[:email]
end
end
s = Student.new(#data)
class << s
alias_method :id, :get_id_override
alias_method :first_name, :get_first_name_override
alias_method :last_name, :get_last_name_override
alias_method :email, :get_email_override
alias_method :contact_info, :get_telephone_override
alias_method :city, :get_city_override
alias_method :state, :get_state_override
alias_method :zipcode, :get_zip_code_override
alias_method :ssn, :get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
I think your question would've been clearer if you posted the code as you wanted it to work. I'm going to suggest an edit.

Should you be defining an instance variable (prefixed by "#") outside of a class definition?
Also, you can't define a method with a period in the name

Related

Converting a class into a hash in Ruby

I am trying to convert any class into a hash using ruby. The initial implementation I have done:
class Object
def to_hash
instance_variables.map{ |v|
Hash[v.to_s.delete("#").to_sym, instance_variable_get(v)] }.inject(:merge)
end
end
Everything seemed to work ok. But when I tried the following code:
class Person
attr_accessor :name, :pet
def initialize(name, pet)
#name = name
#pet = pet
end
end
class Pet
attr_accessor :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
tom = Person.new("Tom", Pet.new("Tobby", 5))
puts tom.to_hash
I have got the following output
{:name=>"Tom", :pet=>#<Pet:0x0055ff94072378 #name="Tobby", #age=5>}
I am unable to hash the attribute pet of type Pet (or any other custom class)
Any ideas?
Edit
That's what I would expect to be returned:
{:name=>"Tom", :pet=>{ :name=>"Tobby", :age=>5}}
When you want to have associated objects to be returned as a hash too hen you have to call to_hash recursively:
class Object
def to_hash
return self if instance_variables.empty?
instance_variables
.map { |v| [v.to_s.delete("#").to_sym, instance_variable_get(v).to_hash] }
.to_h
end
end
tom = Person.new("Tom", Pet.new("Tobby", 5))
puts tom.to_hash
#=> { :name=>"Tom", :pet => { :name=>"Tobby", :age=>5 } }

How to make class methods dynamically using instance variable value

Let see the code first that will help what I want to achieve:
class PostalInfo
attr_reader :name, :code
def initialize (id, name, code)
#id = id
#name = name
#code = code
end
def method_missing(method, *args, &blk)
if method.to_s == "#{name}"
return code
else
super
end
end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
So when I run below code, it gives output as:
pi1.united_states => 'US'
pi2.united_kingdom => 'UK'
its fine upto here, but I also want to do something like
PostalInfo.united_states => 'US'
PostalInfo.united_kingdom => 'UK'
how to do that, thanks in advance
This sets up a class attribute to hold the data, and whenever an instance is initialized, it adds to that data structure, and uses a similar class-level method_missing.
class PostalInfo
attr_reader :name, :code
##postal_info = {}
def self.method_missing(method, *args, &blk)
name = method.to_s
if ##postal_info[name]
##postal_info[name]
else
super
end
end
def initialize (id, name, code)
#id = id
#name = name
#code = code
##postal_info[#name] = #code
end
def method_missing(method, *args, &blk)
if method.to_s == "#{name}"
return code
else
super
end
end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
PostalInfo.united_states #=> 'US'
PostalInfo.united_kingdom #=> 'UK'
I will say, this seems like a weird design, and I'd normally recommend avoiding using mutable state with class methods, and method_missing wherever possible.
You can write something like this:
class PostalInfo
POSTAL_HASH = {
united_states: 'US',
united_kingdom: 'UK',
}.freeze
def self.method_missing(method, *args, &blk)
POSTAL_HASH[method] || super
end
end
Skipping missing method might result in better performance:
class PostalInfo
POSTAL_HASH = {
united_states: 'US',
united_kingdom: 'UK',
}.freeze
class << self
POSTAL_HASH.each do |name, code|
define_method(name) do
code
end
end
end
end
With one exception, you need to mimic the code in the first part of your answer in the class' singleton class. The difference concerns the initialisation of the instance variables. Rather than using PostalInfo::new and PostalInfo#initialize, you need to create a class method for doing that (which I've called add_country_data). Note that as the class' instance variable id is not used I've not included it in the code.
class PostalInfo
class << self
attr_reader :country_data
def add_country_data(name, code)
(#country_data ||= {})[name] = code
end
def add_country_data(name, code)
#country_data[name] = code
end
def method_missing(method, *args, &blk)
return country_data[method.to_s] if country_data.key?(method.to_s)
super
end
end
end
PostalInfo.add_country_data('united_states', 'US')
PostalInfo.add_country_data('united_kingdom', 'UK')
PostalInfo.united_states
#=> "US"
PostalInfo.united_kingdom
#=> "UK"
PostalInfo.france
#=> NoMethodError (undefined method `france' for PostalInfo:Class)
Though this meets your requirement, I would be inclined to construct the class in a more conventional way:
class PostalInfo
attr_reader :name, :code
#instances = []
def initialize(name, code)
#name = name
#code = code
self.class.instances << self
end
singleton_class.public_send(:attr_reader, :instances)
end
us = PostalInfo.new('united_states', 'US')
uk = PostalInfo.new('united_kingdom', 'UK')
us.code
#=> "US"
uk.code
#=> "UK"
PostalInfo.instances
#=> [#<PostalInfo:0x00005c1f24c5ccf0 #name="united_states", #code="US">,
# #<PostalInfo:0x00005c1f24c71858 #name="united_kingdom", #code="UK">]

Ruby Syntax to Populate a new Object's Instance Variables [duplicate]

I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. What I end up doing is something like the following:
class Example
PROPERTIES = [:name, :age]
PROPERTIES.each { |p| attr_reader p }
def initialize(args)
PROPERTIES.each do |p|
self.instance_variable_set "##{p}", args[p] if not args[p].nil?
end
end
end
Is there no more idiomatic way to achieve this? The throw-away constant and the symbol to string conversion seem particularly egregious.
You don't need the constant, but I don't think you can eliminate symbol-to-string:
class Example
attr_reader :name, :age
def initialize args
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c #name="foo", #age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c #name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil
BTW, you might take a look (if you haven't already) at the Struct class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).
HasProperties
Trying to implement hurikhan's idea, this is what I came to:
module HasProperties
attr_accessor :props
def has_properties *args
#props = args
instance_eval { attr_reader *args }
end
def self.included base
base.extend self
end
def initialize(args)
args.each {|k,v|
instance_variable_set "##{k}", v if self.class.props.member?(k)
} if args.is_a? Hash
end
end
class Example
include HasProperties
has_properties :foo, :bar
# you'll have to call super if you want custom constructor
def initialize args
super
puts 'init example'
end
end
e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23
As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.
Struct.hash_initialized
Expanding on Marc-Andre's answer, here is a generic, Struct based method to create hash-initialized classes:
class Struct
def self.hash_initialized *params
klass = Class.new(self.new(*params))
klass.class_eval do
define_method(:initialize) do |h|
super(*h.values_at(*params))
end
end
klass
end
end
# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age
# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>
The Struct clas can help you build such a class. The initializer takes the arguments one by one instead of as a hash, but it's easy to convert that:
class Example < Struct.new(:name, :age)
def initialize(h)
super(*h.values_at(:name, :age))
end
end
If you want to remain more generic, you can call values_at(*self.class.members) instead.
There are some useful things in Ruby for doing this kind of thing.
The OpenStruct class will make the values of a has passed to its initialize
method available as attributes on the class.
require 'ostruct'
class InheritanceExample < OpenStruct
end
example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar')
puts example1.some # => thing
puts example1.foo # => bar
The docs are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html
What if you don't want to inherit from OpenStruct (or can't, because you're
already inheriting from something else)? You could delegate all method
calls to an OpenStruct instance with Forwardable.
require 'forwardable'
require 'ostruct'
class DelegationExample
extend Forwardable
def initialize(options = {})
#options = OpenStruct.new(options)
self.class.instance_eval do
def_delegators :#options, *options.keys
end
end
end
example2 = DelegationExample.new(:some => 'thing', :foo => 'bar')
puts example2.some # => thing
puts example2.foo # => bar
Docs for Forwardable are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html
Given your hashes would include ActiveSupport::CoreExtensions::Hash::Slice, there is a very nice solution:
class Example
PROPERTIES = [:name, :age]
attr_reader *PROPERTIES #<-- use the star expansion operator here
def initialize(args)
args.slice(PROPERTIES).each {|k,v| #<-- slice comes from ActiveSupport
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
I would abstract this to a generic module which you could include and which defines a "has_properties" method to set the properties and do the proper initialization (this is untested, take it as pseudo code):
module HasProperties
def self.has_properties *args
class_eval { attr_reader *args }
end
def self.included base
base.extend InstanceMethods
end
module InstanceMethods
def initialize(args)
args.slice(PROPERTIES).each {|k,v|
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
end
My solution is similar to Marc-André Lafortune. The difference is that each value is deleted from the input hash as it is used to assign a member variable. Then the Struct-derived class can perform further processing on whatever may be left in the Hash. For instance, the JobRequest below retains any "extra" arguments from the Hash in an options field.
module Message
def init_from_params(params)
members.each {|m| self[m] ||= params.delete(m)}
end
end
class JobRequest < Struct.new(:url, :file, :id, :command, :created_at, :options)
include Message
# Initialize from a Hash of symbols to values.
def initialize(params)
init_from_params(params)
self.created_at ||= Time.now
self.options = params
end
end
Please take a look at my gem, Valuable:
class PhoneNumber < Valuable
has_value :description
has_value :number
end
class Person < Valuable
has_value :name
has_value :favorite_color, :default => 'red'
has_value :age, :klass => :integer
has_collection :phone_numbers, :klass => PhoneNumber
end
jackson = Person.new(name: 'Michael Jackson', age: '50', phone_numbers: [{description: 'home', number: '800-867-5309'}, {description: 'cell', number: '123-456-7890'})
> jackson.name
=> "Michael Jackson"
> jackson.age
=> 50
> jackson.favorite_color
=> "red"
>> jackson.phone_numbers.first
=> #<PhoneNumber:0x1d5a0 #attributes={:description=>"home", :number=>"800-867-5309"}>
I use it for everything from search classes (EmployeeSearch, TimeEntrySearch) to reporting ( EmployeesWhoDidNotClockOutReport, ExecutiveSummaryReport) to presenters to API endpoints. If you add some ActiveModel bits you can easily hook these classes up to forms for gathering criteria. I hope you find it useful.

convert Ruby array of class into JSON

I have a Ruby data structure as such:
class Albums
attr_accessor :title, :url
def initialize(title, url)
#title = title
#url = url
end
end
class Albumlist < Albums
attr_accessor :id, :albums
def initialize(id)
#id = id
#albums = Array.new
end
end
And I made an array of Albumlist which looks like (using .inspect method):
[#<Albumlist:0x8db6390 #id="abc",#albums=[
#<Albums:0x8db6098 #title="123", #url="test">,
#<Albums:0x8db5fe4 #title="456", #url="test">
]>,
#<Albumlist:0x8f4042c #id="def", #albums=[
#<Albums:0x8f49f2c #title="Untitled Album", #url="test">
]>
]
I am not sure if this array looks okay but once I used the method .to_json or JSON.generate() on this array, all I get is:
["#<Albumlist:0x8db6390>","#<Albumlist:0x8f4042c>"]
It looks like Ruby returns the "reference" to the object instances.
I want to convert this array in JSON which should look like
[
{
"id":"abc"
"albums":[
{
"title":"123",
"url":"test"
},
{
"title":"456",
"url":"test"
}
]
},
{
"id":"def"
"albums":[
{
"title":"Untitled Album",
"url":"test"
}
]
}
]
and I believe I am missing something, such as converting the array into some kind of intermediate object?
EDIT: Alternate Solution
Define a to_json method in class Albumlist as such:
def to_json(*a)
{
"id" => #id,
"albums" => #albums
}.to_json(*a)
end
And use it instead..
Here it works, probably you are not applying the method on the right moment, let's try it, first, focusing on the array with something like this:
require 'rubygems'
require 'active_support/all'
class Albums
attr_accessor :title, :url
def initialize(title, url)
#title = title
#url = url
end
end
class Albumlist < Albums
attr_accessor :id, :albums
def initialize(id)
#id = id
#albums = Array.new
end
end
a = Albums.new("a", "www.a.com")
b = Albums.new("b", "www.b.com")
c = Albums.new("c", "www.c.com")
list_of_albums_list = Array.new
list_a = Albumlist.new(1)
list_b = Albumlist.new(2)
list_a.albums << a
list_a.albums << b
list_b.albums << c
list_of_albums_list << list_a
list_of_albums_list << list_b
puts list_of_albums_list.to_json
And the output is:
[{"albums":[{"title":"a","url":"www.a.com"},{"title":"b","url":"www.b.com"}],"id":1},{"albums":[{"title":"c","url":"www.c.com"}],"id":2}]
therefore the method works as expected.

Testing a rails model with a singleton in a validate method

Very simple example:
Model:
require 'inventory'
class CustomerOrder < ActiveRecord::Base
validates_presence_of :name
validate :must_have_at_least_one_item, :items_must_exist
before_save :convert_to_internal_items
attr_accessor :items
after_initialize do
#convert the internal_items string into an array
if internal_items
self.items ||= self.internal_items.split(',').collect { |x| x.to_i }
else
# only clobber it if it hasn't been set yet, like in the constructor
self.items ||= []
end
end
private
def convert_to_internal_items
#TODO: convert the items array into a string
self.internal_items = self.items.join(',')
end
def must_have_at_least_one_item
self.items.size >= 1
end
def items_must_exist
self.items.all? do |item|
Inventory.item_exists?(item)
end
end
end
Inventory is a singleton that should provide access to another service out there.
class Inventory
def self.item_exists?(item_id)
# TODO: pretend real code exists here
# MORE CLARITY: this code should be replaced by the mock, because the actual
# inventory service cannot be reached during testing.
end
end
Right now the service does not exist, and so I need to mock out this method for my tests. I'm having trouble doing this the Right Way(tm). I'd like to have it be configurable somehow, so that I can put in the mock during my tests, but have the normal code run in the real world.
There's probably something I'm not wrapping my head around correctly.
EDIT: to be more clear: I need to mock the Inventory class within the validation method of the model. Eventually that will talk to a service that doesn't exist right now. So for my tests, I need to mock it up as if the service I were talking to really existed. Sorry for the confusion :(
Here's what I'd like to have in the specs:
describe CustomerOrder do
it "should not accept valid inventory items" do
#magical mocking that makes Inventory.item_exists? return what I want
magic.should_receive(:item_exists?).with(1).and_return(false)
magic.should_receive(:item_exists?).with(2).and_return(true)
co = CustomerOrder.new(:name => "foobar", :items => [1,2]
co.should_not be_valid
end
it "should be valid with valid inventory items" do
#magical mocking that makes Inventory.item_exists? return what I want
magic.should_receive(:item_exists?).with(3).and_return(true)
magic.should_receive(:item_exists?).with(4).and_return(true)
co = CustomerOrder.new(:name => "foobar", :items => [3,4]
co.should be_valid
end
end
Using rails 3.0.3, rspec 2 and cucumber. Of course, only the rspec part matters.
require 'spec_helper'
describe CustomerOrder do
it "is invalid without an existing Inventory item" do
item = mock('item')
customer = Customer.new(:name=>"Moe")
customer.stub(:items) { [item] }
Inventory.should_receive(:item_exists?).with(item).and_return(true)
customer.should_not be_valid
end
end
Note: untested.
The way I ended up solving this follows
Inventory class:
require 'singleton'
class Inventory
include Singleton
def self.set_mock(mock)
#mock = mock
end
def self.item_exists?(item_id)
return #mock.item_exists?(item_id) if #mock
# TODO: how should I stub this out for the api
end
end
CustomerOrder model:
require 'inventory'
class CustomerOrder < ActiveRecord::Base
validates_presence_of :name
validate :must_have_at_least_one_item, :items_must_exist
before_save :convert_to_internal_items
attr_accessor :items
after_initialize do
#convert the internal_items string into an array
if internal_items
self.items ||= self.internal_items.split(',').collect { |x| x.to_i }
else
# only clobber it if it hasn't been set yet, like in the constructor
self.items ||= []
end
end
private
def convert_to_internal_items
#TODO: convert the items array into a string
self.internal_items = self.items.join(',')
end
def must_have_at_least_one_item
errors.add(:items, "Must have at least one item") unless self.items.size >= 1
end
def items_must_exist
failed = self.items.find_all do |item|
!Inventory.item_exists?(item)
end
if !failed.empty? then
errors.add(:items, "Items with IDs: [#{failed.join(' ')}] are not valid")
end
end
end
CustomerOrder specs:
require 'spec_helper'
describe CustomerOrder do
fixtures :all
before do
fake = double('fake_inventory')
fake.stub(:item_exists?) do |val|
case val
when 1
true
when 2
true
when 3
false
end
end
Inventory.set_mock(fake)
#GRR, skipping my fixtures right now
#valid_order = CustomerOrder.new(:name => "valid order",
:items => [1,2])
end
it "should require a name and at least one item" do
co = CustomerOrder.new(:name => "valid", :items => [1])
co.should be_valid
end
it "should not be valid without any items" do
#valid_order.items = []
#valid_order.should_not be_valid
end
it "should not be valid without a name" do
#valid_order.name = nil
#valid_order.should_not be_valid
end
it "should expose items instead of internal_items" do
#valid_order.should respond_to(:items)
end
it "should be able to treat items like an array" do
#valid_order.items.size.should == 2
#valid_order.items.should respond_to(:<<)
#valid_order.items.should respond_to(:[])
end
it "should store items internally as a comma separated string" do
co = CustomerOrder.new(:name => "name", :items => [1,2])
co.save!
co.internal_items.should == "1,2"
end
it "should convert items to internal_items for saving" do
co = CustomerOrder.new(:name => "my order",
:items => [1,2])
co.name.should == "my order"
co.save!
co.internal_items.should == "1,2"
end
it "loads items from the database into the items array correctly" do
co = CustomerOrder.new(:name => "woot", :items => [2,1])
co.save.should == true
co2 = CustomerOrder.find_by_name("woot")
co2.items.should == [2,1]
end
it "is not valid with items that don't exist" do
#valid_order.items = [3,2,1]
#valid_order.should_not be_valid
end
it "ensures that items exist to be valid" do
#valid_order.items = [1,2]
#valid_order.should be_valid
end
end
This solution works, although it's probably not the best way to inject a mock into the Inventory Service at runtime. I'll try to do a better job of being more clear in the future.

Resources