I want to "reinitialize" my User class, so i have a fresh state in each of my RSpec examples. I tried calling remove_const:
before(:each) do
Object.send(:remove_const, 'User')
load 'user.rb'
end
describe 'initializer' do
it 'creates an user' do
user = User.new("jules", "jules#gg.k")
expect(user.class).to eq(User)
end
it 'saves #email as instance variable' do
email = "jules#gg.com"
user = User.new("jules", email)
expect(user.email).to eq(email)
end
# ...
end
but it returns:
NameError: constant Object::User not defined
My User class looks like this:
class User
attr_accessor :name, :email
##user_list = []
##user_count = 0
def self.user_count
##user_count
end
def self.all
##user_list
end
def initialize(name, email)
#email = email
#name = name
##user_count += 1
##user_list << self
end
end
I see two options:
only remove the constant if it is actually defined:
if Object.const_defined?(:User)
Object.send(:remove_const, :User)
load 'user.rb' # <- you may have to adjust the path
end
add a class method to User to clear your in-memory user "database":
class User
def self.clear
##user_list = []
##user_count = 0
end
end
and call that instead:
before(:each) do
User.clear
end
I'd go with option #2.
require_relative '../lib/user'
describe User do
before(:each) do
Object.send(:remove_const, 'User')
load 'user'
end
describe 'initializer' do
it 'creates an user' do
user = User.new("jules", "jules#gg.k")
expect(user.class).to eq(User)
end
it 'saves #email as instance variable' do
email = "jules#gg.com"
user = User.new("jules", email)
expect(user.email).to eq(email)
end
it 'adds one to the ##user_count global variable' do
count = User.user_count
user = User.new("jules", "email#email.email")
expect(User.user_count).to eq(count+1)
end
end
describe 'classe method' do
describe 'all' do
it 'should return a list of names of all users' do
user1 = User.new("jacques", "jacky#chan.fr")
user2 = User.new("phil", "ph#il.ipe")
expect(User.all.map! {|k| k.name}).to eq(["jacques", "phil"])
end
end
describe 'find_by_email' do
it 'should return return a name if user exists and nil otherwise' do
user1 = User.new("jacques", "jacky#chan.fr")
user2 = User.new("phil", "ph#il.ipe")
expect(User.find_by_email("jacky#chan.fr")).to eq("jacques")
end
end
end
end
Related
I am trying to achieve this feature:
Sent texts out to new authors.
It enables admin users to
when authors published their first article in a specified timeframe, Compose and send out emails to authors
Download a CSV file with a list of recipients.
I want to refactor the controller code in efficient way
Models
class Email < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :articles
scope :recent, -> { order("created_at DESC") }
end
class Article < ActiveRecord::Base
end
# Controller
class EmailsController < ApplicationController
attr_accessor :email
def new
#email = Email.new
if params[:date_from].present? && params[:date_to].present?
fetch_users
end
end
def create
#email = Email.new(email_params)
users = fetch_users
recipients = []
users.each do |user|
recipients << user.email
end
if #email.save && send_mail_to_user(recipients)
redirect_to emails_path, notice: "Emails Sent Successfully!"
end
end
def download_csv
users = fetch_users
send_data to_csv(users), filename: "users-article-#{Time.zone.today}.csv"
end
private
def to_csv(data)
inputs = %w{id email name}
CSV.generate(headers: true) do |csv|
csv << attributes
data.each do |user|
csv << inputs.map { |attr| user.send(attr) }
end
end
end
def send_mail_to_user(users)
users.each do |r|
UserMailer.bulk_email(r).deliver_later
end
end
def fetch_users
#users = User.recent.joins(:articles).group("articles.user_id").where("`published_article_count` = 1").
where("published_at Between ? AND ?", Date.parse(params[:date_from]), Date.parse(params[:date_to])).page(params[:page])
end
def email_params
params.permit(:body, :date_from, :date_to)
end
end
I am trying to get programm started where i cant read in a csv File an it prints the data out on a pdf-File. Now i have a problem.
Heres is my Code:
------------------------------------
require_relative 'friends'
class List
attr_accessor :list_name, :list
def initialize(list_name)
#list_name = list_name
#list = []
end
def list_name
#list_name
end
def liste
#list
end
def wert(place)
#list[place].to_s
end
def list_length
#list.length
end
def print_list
#list.each do |freunde|
"#{freunde.name},#{freunde.age}"
end
end
def add_friend(a_friend)
#list.push(a_friend)
end
def load_friend(from_file)
File.readlines(from_file).each do |line|
add_friend(Freunde.from_csv(line))
end
end
end
-------------------------------------------
require_relative 'list'
class Friends
attr_accessor :name,:age
def initialize(name, age)
#name = name
#age = age
end
def self.from_csv(string)
name, age = string.split(',')
Freunde.new(name,age)
end
def friends
#name
end
end
-------------------------------------------
require 'prawn'
require_relative 'list'
require_relative 'friends'
class Generating
include Prawn::View
def initialize
#document = Prawn::Document.new(:page_size => "A4")
#fontpath = File.expand_path("../data/fonts", __FILE__)
liste1 = Listen.new("Friendslist")
liste1.load_friend("test.csv")
print_list
save
end
def print_friends
font("#{#fontpath}/Arial.ttf") do
font_size 11
text_box("#{liste1.print_list}", :at => [15,405], :height => 50,
:width => 250)
end
end
def save
self.render_file "Hello.pdf"
end
end
---------------------------------------------
When i now create a new generating-Object:
gen = Generating.new
then it fails the whole programm because the error says method unknow (print_list). Am i submitting the wrong object for the method(print_list), or am using the text output methods of prawn wrong?
print_list is an instance method of List class, and you call it on self object, which is there an instance of Generating. It should be:
liste1 = Listen.new("Friendslist")
liste1.load_friend("test.csv")
#⇓⇓⇓⇓⇓⇓
liste1.print_list
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names
If I have a specific value assigned to an instance variable, how do I define a method to reassign the value of this variable? When I run the code below in rspec, I keep getting the original value.
class Test
def name
#name = 'me'
end
def name=(input)
#name = input
end
end
def name
#name = 'me'
end
Every time you call the method above, you set #name to 'me' and return it.
I believe you are looking for the ||= operator
def name
#name ||= 'me' # only set #name to 'me' if it is not already set
end
IMO, the best way to accomplish a default value for #name is:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end
example:
t = Test.new
t.name
# => "me"
t.name = 'foo'
# => "foo"
t.name
# => "foo"
Because you're setting the #name variable in the getter, where you should only be returning it. Like so:
class Test
def name
#name
end
def name=(input)
#name = input
end
end
Or more simply you should just use the attr_accessor method to declare boilerplate versions of the getter and setter methods. Like so:
class Test
attr_accessor :name
end
The initial value should be set in the constructor method.
class Test
def initialize
#name = 'me'
end
def name
#name
end
def name=(input)
#name = input
end
end
And you could use attr_accessor to make you code simple:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end
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.