How to write a rspec testing for hash - ruby-on-rails

Trying to write rspec test for code below. The spec suppose to test that the right key is passed. Do I make a hash let variable and expect the key to equal the variable key. I am not really sure how to set it up.
def something_att
Hash[
%i(name age).map do |k|
[k, send(k)]
end
]
end

first version something_att without parameters
it "test something_att" do
hash_result = something_att
expect(hash_result).to eq(
name: 'john doe',
age: 40
)
end
second version something_att with parameters
(but you have to change how something_att methods)
I prefer to use this since it's more flexible to use with params you can set
it "test something_att" do
hash_data = { name: 'john doe', age: 20 }
hash_result = something_att(hash_data)
expect(hash_result).to eq(
name: 'john doe',
age: 40
)
end

As you said, The spec supposes to test that the right key is passed.
describe "#something_att" do
it "should have right keys" do
expect(something_att.keys.sort).to eq ['name', 'age'].sort
end
end

Related

Conditionally include attribute is create method - Rails 5

What would be the best way of conditionally including an attribute in a create (or any method(attr, attr, attr) style method)?
#user = User.find_by_name("Sam") # <- might not exist
Store.create(
name: "Some Store",
email: "store#example.com",
user_id: #user.id if #user.present? # <- CONCEPT ONLY - Should be added if condition is true to prevent error
)
the line user_id: #user.id throws an error if #user wasn't found and is therefore nil.
You forgot that .create()'s argument is a shortcut for a hash. Make the hash explicit:
hash = {
name: "Some Store",
email: "store#example.com"
}
hash[:user_id] = #user.id if #user.present?
Store.create(hash)
Another option is to pass .create a block:
Store.create do |s|
s.name = "Some Store"
s.email = "store#example.com"
s.user_id = #user.id if #user
end
The most minimal change I could make for your code to work is:
Store.create(
name: "Some Store",
email: "store#example.com",
user_id: #user&.id
)
This is using the safe navigation operator (&.) to "ignore method calls" on nil values.
However, why are you fetching a possibly-nil object and then trying to call a method on it? Given the context of your original post, you could instead just do:
Store.create(
name: "Some Store",
email: "store#example.com",
user: #user
)
This will work fine, regardless of whether or not the #user exists.
...Or perhaps even, what you really intend to do is something like:
Store.create(
name: "Some Store",
email: "store#example.com",
user: User.find_or_create_by(name: 'Sam')
)

Writing test for Spree, cant create variants for products

I'm trying to write rspec tests for my spree customizations and i need to create products with variants. i cant seem to do this even though i appear to be doing the exact same thing as the rspec tests that are part of spree core.
def build_option_type_with_values(name, values)
ot = create(:option_type, :name => name)
values.each do |val|
ot.option_values.create(:name => val.downcase, :presentation => val)
end
ot
end
let(:number_size_option_type) do
size = build_option_type_with_values("number sizes", %w(1 2 3 4))
end
let(:product1) { create(:product, name: 'product1') }
it "should have variants" do
hash = {number_size_option_type.id.to_s => number_size_option_type.option_value_ids}
product1.option_values_hash = hash
product1.save
product1.reload
expect(product1.variants.length).to eq(4)
end
no matter what i do, the number of variants for my product is always zero.
Turns out the product.option_values_hash needs to be added during product creation in order to invoke the variant creation code. here is the changed line and then i removed the hash from the test "should have variant"
let(:product1) { create(:product, name: 'product1', option_values_hash: {number_size_option_type.id.to_s => number_size_option_type.option_value_ids}) }
it "should have variants" do
product1.save
expect(product1.option_type_ids.length).to eq(1)
expect(product1.variants.length).to eq(4)
end

put_via_redirect need quick example

i want an example of put_via_redirect
my code
describe "PUT /authors/1" do
it "will update author with id 1" do
put_via_redirect( author_path(Author.find(1)), {:author => { name: "tim"}} )
expect(response.body).to include("Tim")
end
# ....
end
Active_record not found error
Your authors table is empty. Try populating a sample record before checking the response.
Author.create(id: 1, name: 'Tim')

RSpec assigns not working as expected

I have the following spec for the controller in simple ActiveRecord search feature:
Spec:
it "returns the records that match the given due date" do
create(:task, due_date: '2013-01-01')
create(:task, due_date: '2014-01-01')
get :search, 'filter' => { due_date: '2013-01-01' }
expect(assigns(:tasks)).to \
eq Task.search('filter' => { due_date: '2013-01-01' })
end
The model and controller are simple:
Model:
def self.search(params)
result = self.all #I know this is a bad idea, but starting simple.
params.each do |field, criteria|
if field.match(/due_date|completed_date/) && criteria != nil
result = result.where("DATE(#{field}) = ?", criteria)
end
end
result
end
Controller action:
def search
#tasks = Task.search(params['filter'])
#output from when the spec runs below
#puts params -> {"filter"=>{"due_date"=>"2013-01-01"}, \
# "controller"=>"tasks", \
# "action"=>"search"}
#puts params['filter] -> {"due_date"=>"2013-01-01"}
#puts #tasks.inspect -> just the one record
render 'index'
end
The spec fails, but it appears that it fails because the controller is returning both objects, while Task.search(...) is returning only the object with the specified value for due_date, as expected.
Here is the error message (edited for length):
2) TasksController GET #search returns the records that
match the given due date
Failure/Error: expect(assigns(:tasks)).to
eq Task.search('filter' => { due_date: '2013-01-01' })
expected: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">,
#<Task id: 2, due_date: "2014-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
got: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
You would assume that since the model apparently works (as evidenced by this result and a separate model spec that passes) that there is something wrong with the controller, but the controller is dead simple. I also have a feature spec incorporating the same controller that submits a form, triggers the search action and looks at the output, and the output only includes the one, correct record.
Am I missing something about how assigns works, making a dumb mistake or other?
It was option B, dumb mistake.
The model method takes the value of the filter element of the params hash as an argument, not the fake params hash I need to send to GET #searchin the line above the expectation. Or more clearly maybe, replace:
expect(assigns(:tasks)).to eq Task.search('filter' => { due_date: '2013-01-01' })
with
expect(assigns(:tasks)).to eq Task.search(due_date: '2013-01-01')'

creating name helper, to split first and last names apart

I'm looking for some help on how to take an attribute and process it through a method to return something different. But I've never done this before and I' not sure where to start. I thought trying to change a name:string attribute from "George Washington" or "John Quincy Adams" into first names only "George" and "John".
I thought maybe a helper method would be best, such as
users_helper.rb
def first_name
end
and then call #user.name.first_name, would this be initially how it would work? Can someone explain where I'd go next to be able to pass #user.name into the method? I've seen things like this but don't quite understand it the parenthesis...
def first_name(name)
puts name
end
Could someone breakdown how rails/ruby does this type of thing? Thanks a lot!
Some people have more than two names, such as "John Clark Smith". You can choose to treat them as:
(1) first_name: "John", last_name: "Smith"
def first_name
if name.split.count > 1
name.split.first
else
name
end
end
def last_name
if name.split.count > 1
name.split.last
end
end
(2) first_name: "John Clark", last_name: "Smith"
def first_name
if name.split.count > 1
name.split[0..-2].join(' ')
else
name
end
end
def last_name
if name.split.count > 1
name.split.last
end
end
(3) first_name: "John", last_name: "Clark Smith"
def first_name
name.split.first
end
def last_name
if name.split.count > 1
name.split[1..-1].join(' ')
end
end
The above examples assume that if the name contains less than 2 words then it is a first name.
The parentheses (which are optional) enclose the parameter list.
def first_name(full_name)
full_name.split(" ")[0]
end
This assumes the parameter is not nil.
> puts first_name "Jimmy McKickems"
Jimmy
> puts first_name "Jeezy"
Jeezy
But this is not a string method, as your assumption is now:
#user.full_name.first_name # Bzzt.
Instead:
first_name #user.name
This could be wrapped up in the model class itself:
class User < ActiveRecord
# Extra stuff elided
def first_name
self.full_name.blank? ? "" : self.full_name.split(" ")[0]
end
end
The extra code checks to see if the name is nil or whitespace (blank? comes from Rails). If it is, it returns an empty string. If it isn't, it splits it on spaces and returns the first item in the resulting array.
In case you are looking to split only once and provide both parts this one liner will work:
last_name, first_name = *name.reverse.split(/\s+/, 2).collect(&:reverse)
Makes the last word the last name and everything else the first name. So if there is a prefix, "Dr.", or a middle name that will be included with the first name. Obviously for last names that have separate words, "Charles de Gaulle" it won't work but handling that is much harder (if not impossible).
Use Ruby's Array#pop
For my needs I needed to take full names that had 1, 2, 3 or more "names" in them, like "AUSTIN" or "AUSTIN J GILLEY".
The Helper Method
def split_full_name_into_first_name_and_last_name( full_name )
name_array = full_name.split(' ') # ["AUSTIN", "J", "GILLEY"]
if name_array.count > 1
last_name = name_array.pop # "GILLEY"
first_name = name_array.join(' ') # "AUSTIN J"
else
first_name = name_array.first
last_name = nil
end
return [ first_name, last_name ] # ["AUSTIN J", "GILLEY"]
end
Using It
split_full_name_into_first_name_and_last_name( "AUSTIN J GILLEY" )
# => ["AUSTIN J", "GILLEY"]
split_full_name_into_first_name_and_last_name( "AUSTIN" )
# => ["AUSTIN", nil]
And you can easily assign the first_name and last_name with:
first_name, last_name = split_full_name_into_first_name_and_last_name( "AUSTIN J GILLEY" )
first_name
# => "AUSTIN J"
last_name
# => "GILLEY"
You can modify from there based on what you need or want to do with it.
For the syntax you're asking for (#user.name.first_name) Rails does a lot of this sort of extension by adding methods to base types, in your example you could do this through defining methods on the String class.
class String
def given; self.split(' ').first end
def surname; self.split(' ').last end
end
"Phileas Fog".surname # 'fog'
Another way to do something like this is to wrap the type you whish to extend, that way you can add all the crazy syntax you wish without polluting more base types like string.
class ProperName < String
def given; self.split(' ').first end
def surname; self.split(' ').last end
end
class User
def name
ProperName.new(self.read_attribute(:name))
end
end
u = User.new(:name => 'Phileas Fog')
u.name # 'Phileas Fog'
u.name.given # 'Phileas'
u.name.surname # 'Fog'
Just as complement of great Dave Newton's answer, here is what would be the "last name" version:
def last_name
self.full_name.blank? ? "" : self.full_name.split(" ")[-1]
end
making it simple
class User < ActiveRecord::Base
def first_name
self.name.split(" ")[0..-2].join(" ")
end
def last_name
self.name.split(" ").last
end
end
User.create name: "John M. Smith"
User.first.first_name
# => "John M."
User.first.last_name
# => "Smith"
Thanks
def test_one(name)
puts "#{name.inspect} => #{yield(name).inspect}"
end
def tests(&block)
test_one nil, &block
test_one "", &block
test_one "First", &block
test_one "First Last", &block
test_one "First Middle Last", &block
test_one "First Middle Middle2 Last", &block
end
puts "First name tests"
tests do |name|
name.blank? ? "" : name.split(" ").tap{|a| a.pop if a.length > 1 }.join(" ")
end
puts "Last name tests"
tests do |name|
name.blank? ? "" : (name.split(" ").tap{|a| a.shift }.last || "")
end
Output:
First name tests
nil => ""
"" => ""
"First" => "First"
"First Last" => "First"
"First Middle Last" => "First Middle"
"First Middle Middle2 Last" => "First Middle Middle2"
Last name tests
nil => ""
"" => ""
"First" => ""
"First Last" => "Last"
"First Middle Last" => "Last"
"First Middle Middle2 Last" => "Last"

Resources