writing a rspec test to add an item to a hash - ruby

I am getting a NoMethodError
for my code but I have defined the add method it says it is missing.
I am trying to add an item to a hash that already exists.
The hash is the dishes and I am trying to use the add method.
The test:
require 'menu'
describe Menu do
it 'has a menu' do
expect(subject.respond_to?(:dishes)).to be true
end
it 'displays dishes and prices' do
expect(subject.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
it 'can add dishes to it' do
menu = Menu.new
menu.add_dish("Icecream", 4.80)
expect(subject.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 },
{ name: 'icecream', price: 4.80 }
]
end
end
the methods
class Menu
def initialize
#dishes = []
end
def dishes
#dishes = [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
def add_dish(name, price)
#dishes << { name: name, price: price }
end
end
Thank you

The answer of Ryan-Neal Mes solves the NoMethodError, but there are many other problems in your code.
You repeat your self, and you should make your code dry (Don't Repeat Yourself principle)
while you want to add a hash to the list of dishes which is it self a list of hashes, you force the object that needs to call the add method to provide the parameters in a particular order, than the method constructs the hash, so every time you need to call it you need to return to it to see the order of parameters.
the dishes method is wrong, because each time you call it, it assigns the initial array to the #dishes variable. In this case the add_dishes method will have no effect, since the added dish will be deleted the next time you call the dishes method.
your examples are not expressive, so if a test did not pass, you cannot know from the printed messages what's the problem. OK, this is not a big deal in this small example, but in a big application, specs expressiveness is of a higher value.
here the test examples
require 'menu'
describe Menu do
# every time you call the function dishes in an example
# it will be declared and it will return this array
let :dishes do
[
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
# explicit definition of the subject
subject { Menu.new }
# a shorter yet more expressive version of
# expect(subject.respond_to?(:dishes)).to be true
it { is_expected.to respond_to(:dishes) }
# You should always group the examples that test
# the same method
describe '#dishes' do
# it 'displays dishes and prices' do
it 'returns the list of dishes' do
expect(subject.dishes).to eq dishes
end
end
describe "#add_dish" do
# it 'can add dishes to it' do
it "adds the given dish to the list of dishes" do
new_dish = {name: 'salad', price: 4.0 }
expect {
subject.add_dish(new_dish)
}.to change(subject.dishes, :count).by(1)
expect(subject.dishes).to include new_dish
end
end
end
so here is the class definition
class Menu
# you don't need to declare the method dishes
# since this is what attr_reader will do
attr_reader :dishes
def initialize
# this will set the #dishes only once
# but you code #dishes = [...] will return
# the same list every time you call it and
# all the dishes you add through the #add method
# will be deleted.
#dishes = [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
# dish is a hash {name: '', price: ''}
def add_dish(dish)
#dishes << dish
end
end
so now run rspec --format doc --color and see who expressive are the messages.

It's a bit difficult to get this working without your code, but the problem is pretty straight forward.
Try the edited code below. Note the changes to the spec initialize the menu and add method adds to the instance variable #dishes.
require 'menu'
describe Menu do
it 'displays dishes and prices' do
expect(Menu.new.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
it 'can add dishes to it' do
menu = Menu.new.add("Icecream", 4.80)
expect(menu.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 },
{ name: 'icecream', price: 4.80 }
]
end
end
class Menu
def initialize
#dishes = []
end
def dishes
#dishes ||=
[
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
def add(name, price)
#dishes << { name: name, price: price }
end
end
Hope this helps

It looks like you've got a couple of problems with this code. First, because the add method is not declared as a class method (i.e. def self.add) you can't call it as a class method (as you've seen, Menu.add says NoMethodError). Instead, you'll need to create an instance of the Menu class in your test, perhaps using let:
describe Menu do
let(:menu) { Menu.new }
it 'can add dishes to it' do
menu.add("Icecream", 4.80)
# test your expectations...
end
end
Lastly, as the add method is currently defined, it doesn't modify #dishes but rather just returns a new hash, so your expectation will fail. You'll need to make the add method append the values, perhaps like this:
def add(name, , price)
#dishes << {name: name, price: price}
end

Ah I see your problem. You need to initialize your menu. Add is not a static method. So you need something like,
Menu.new.add(blah, blah)
Look at:
Menu.add("Icecream", 4.80)
This method is wrong. It needs to be:
Menu.new.add("Icecream", 4.80)
or you need something like:
menu = Menu.new
menu.add("Icecream", 4.80)

Related

How to add element on graphql return fields

Im a newbie in Ruby and GraphQL
Currently i have such Mutations module
module Mutations
class ProductCreate < BaseMutation
# TODO: define return fields
# field :post, Types::PostType, null: false
type Types::ProductType
# TODO: define arguments
argument :title, String, required: true
argument :vendor, String, required: false
argument :store, ID, required: true
# TODO: define resolve method
def resolve(title:, vendor:, store:)
Product.create!(title: title, vendor: vendor, store: store)
end
end
end
and when i call
mutation {
productCreate(input: {store:"61d6f33a58c4dc4e8a1a0536", title: "Sweet new product", vendor: "JadedPixel"})
{
_id
}
}
Result is
{
"data": {
"productCreate": {
"_id": "61de591c58c4dcb08dffafa9"
}
}
}
I would like to add additional paramenter to query and also get additional paramenter in result
So, my question is
What should i change in code
mutation {
productCreate(input: {title: "Sweet new product", productType: "Snowboard", vendor: "JadedPixel"}) {
product {
id
}
}
}
to get result like this
{
"productCreate": {
"product": {
"id": "1071559610"
}
}
}
I found solutions
Just need to change code like this
module Mutations
class ProductCreate < BaseMutation
field :product, Types::ProductType, null: true
# TODO: define arguments
argument :title, String, required: true
argument :vendor, String, required: false
argument :store, ID, required: true
# TODO: define resolve method
def resolve(title:, vendor:, store:)
record = Product.create!(title: title, vendor: vendor, store: store)
{ product: record }
end
end
end
source of an example
https://www.keypup.io/blog/graphql-the-rails-way-part-2-writing-standard-and-custom-mutations

Ruby Nested Hash Merge

Given something like this:
hey = {
some_key: {
type: :object,
properties: {
id: { type: :string, example: '123', description: 'Id' },
created_at: { type: :string, example: '2019-02-14 14:13:55'},
updated_at: { type: :string, example: '2019-02-14 14:13:55'},
type: { type: :string, example: 'something', description: 'Resource type' },
token: { type: :string, example: 'token', description: 'Some description of token' }
}
}
}
I would like to go through all keys until I find one named properties, then mutate its content such that the keys become the value of a description key if it doesn't exit in its nested hash.
So for the example above, the hash would end up like this:
hey = {
some_key: {
type: :object,
properties: {
id: { type: :string, example: '123', description: 'Id' },
created_at: { type: :string, example: '2019-02-14 14:13:55', description: 'Created At'},
updated_at: { type: :string, example: '2019-02-14 14:13:55', description: 'Updated At'},
type: { type: :string, example: 'something', description: 'Resource type' },
token: { type: :string, example: 'token', description: 'Some description of token' }
}
}
}
created_at and updated_at didn't have a description.
It should also handle if token, for instance, had a properties property.
I came up with a solution that works but I am really curious on how I can improve it?
My solution below:
def add_descriptions(hash)
return unless hash.is_a?(Hash)
hash.each_pair do |key, value|
if key == :properties
value.each do |attr, props|
if props[:description].nil?
props.merge!(description: attr.to_s)
end
end
end
add_descriptions(value)
end
end
As I understand all you know about the hash hey is that it is comprised of nested hashes.
def recurse(h)
if h.key?(:properties)
h[:properties].each do |k,g|
g[:description] = k.to_s.split('_').map(&:capitalize).join(' ') unless
g.key?(:description)
end
else
h.find { |k,obj| recurse(obj) if obj.is_a?(Hash) }
end
end
recurse hey
#=> {:id=>{:type=>:string, :example=>"123", :description=>"Id"},
# :created_at=>{:type=>:string, :example=>"2019-02-14 14:13:55",
# :description=>"Created At"},
# :updated_at=>{:type=>:string, :example=>"2019-02-14 14:13:55",
# :description=>"Updated At"},
# :type=>{:type=>:string, :example=>"something",
# :description=>"Resource type"},
# :token=>{:type=>:string, :example=>"token",
# :description=>"Some description of token"}}
The return value is the updated value of hey.

rspec expect symbol not equal to created object

I wrote a test for controll with rspec:
it "populates an array of books" do
book = FactoryGirl.create(:book)
get :index
expect(:books).to eql([book])
end
books_controller.rb
def index
#books = Book.all.order("created_at DESC")
end
books.rb
FactoryGirl.define do
factory :book do |f|
f.name { Faker::Book.title }
f.author { Faker::Book.author }
f.press { Faker::Book.publisher }
f.cover { fixture_file_upload(Rails.root.join('spec', 'photos', 'testcover.jpg'), 'image/png') }
end
end
Run bin/rake spec, the result is:
1) BooksController GET #index populates an array of books
Failure/Error: expect(:books).to eql([book])
expected: [#<Book id: 1, name: "The Waste Land", author: "谢靖琪", isbn: nil, press: "University of Chicago Press"...e: "image/png", cover_file_size: 104531, cover_updated_at: "2016-08-26 04:00:19", page_number: nil>]
got: :books
(compared using eql?)
Diff:
## -1,2 +1,2 ##
-[#<Book id: 1, name: "The Waste Land", author: "谢靖琪", isbn: nil, press: "University of Chicago Press", description: nil, grade_level: nil, lexile_level: nil, douban_link: nil, scholastic_link: nil, created_at: "2016-08-26 04:00:19", updated_at: "2016-08-26 04:00:19", cover_file_name: "fcbcb7417dbc88827d16765a.jpg", cover_content_type: "image/png", cover_file_size: 104531, cover_updated_at: "2016-08-26 04:00:19", page_number: nil>]
+:books
They are not equal. It seems that the first expected result, '...' is an abbreviation. How can I fix it?
I think what you want is
expect(assigns(:books)).to eq([book])

Rails New Field Not Appear

I just got an issue after I've done a migration for a table, I've added owner field to the project table, but it is not appear when I request it (project).
Expected response after migration:
{
projects: [
{ id: 1, name: "First proj", owner: 1 },
{ id: 2, name: "Second proj", owner: 1 }
]
}
I got:
{
projects: [
{ id: 1, name: "First proj" },
{ id: 2, name: "Second proj" }
]
}
Here is my migration file
class AddOwnerRefToProjects < ActiveRecord::Migration
def change
add_reference :projects, :owner, index: true
end
end
My projects model
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => :User
end
You need to add the new attribute in your json serializer.

RABL - How to create a custom child node as collection?

{
total: 250,
page: 3,
data: [
{ id: 1, name: "Foo", ...},
{ id: 2, name: "Bar", ...}
]
}
I want to create structure like this. Value of id and name are random. It is not saved any variable.
If all you are searching is how to make custom nodes is just with node method.When you declare object to be false you are free to make just custom repsonse.Here is a solution where I generate ids from 1 to 100, and name based on simple conversation to hexademical numbers.
object false
node(:total) { |m| #total }
node(:page) { |m| #page }
node(:data) do |m|
1.upto(100).map { |id| Hash[[[:id, id], [:name, (id * 143223).to_s(16)]]] }
end

Resources