How to determine if an object was 'first' or 'created'? - ruby

I have refactored some code in a long running rake task. I used to have this code:
if object = Object.find_by_name(name: "string")
else
object = Object.create(name: "string")
puts "#{object.name} created"
end
Now I have this code:
object = Object.where(name: "string").first_or_create
I figure this is an improvement. But, I am really missing the puts statement in the long running rake task. How can I determine if the object was 'first' or 'created' when using first_or_create?

You could use
object .persisted? or object .new_record? to check it.
object .persisted? returns true if the object is already in db,
object.new_record? returns true if the object is newly created.

Adding on top of #xdazz's answer, ActiveRecord has
obj = Model.find_or_create_by_name("String")
obj.persisted? #=> false
obj.save
obj = Model.find_or_create_by_name("String")
obj.persisted? #=> true
This method does the same thing you're doing.

Related

RSpec mock method inside of select loop

I want to test simple class which iterate through array of hashes and return only those with status Pending which were updated more than 2 days ago.
class FetchPending
PROJECT_KEY = 'TPFJT'
TWO_DAYS = Time.now - 2 * 24 * 60 * 60
def call
project.select do |issue|
issue.fields.dig('status', 'name') == 'Pending' &&
DateTime.parse(issue.fields.dig('updated')) < TWO_DAYS
end
end
private
def project
#project ||= Jira::ProjectConnection.new(PROJECT_KEY).call
end
end
How to test fields method which is a method of Jira-Ruby gem. I think it comes from here (Field class in resource of gem) because nowhere else have I found fields method.
Here are my thoughts after debugging:
project.class - Array
issue.class - JIRA::Resource::Issue
my natural thinking was:
before do
# (...) some other mocks
allow(JIRA::Resource::Issue).to receive(:fields)
end
But I'm getting an error:
Failure/Error: allow(JIRA::Resource::Issue).to receive(:fields)
JIRA::Resource::Issue does not implement: fields
I have been struggling with this problem for DAYS, I'm pretty desperate here. How to mock this method?
Here is my rest of my specs:
RSpec.describe FetchPending do
subject { described_class.new }
let(:project_hash) do
[
{
'key': 'TP-47',
'fields': {
'status': {
'name': 'Pending'
},
'assignee': {
'name': 'michael.kelso',
'emailAddress': 'michael.kelso#example.com'
},
'updated': '2020-02-19T13:20:50.539+0100'
}
}
]
end
let(:project) { instance_double(Jira::ProjectConnection) }
before do
allow(Jira::ProjectConnection).to receive(:new).with(described_class::PROJECT_KEY).and_return(project)
allow(project).to receive(:call).and_return(project_hash)
allow(JIRA::Resource::Issue).to receive(:fields)
end
it 'return project hash' do
expect(subject.call).include(key[:'TP-47'])
end
and_return is generally used for returning a value (such as a string or an integer) or sequence of values, but for objects you sometimes need use a block. Additionally, if call is a valid method on a Jira::ProjectConnection object that returns the value of project_hash, you can directly mock its behavior when declaring your instance double (this functionality is unclear from the Relish docs bc they are kinda terrible). Something like this will probably work:
let(:project) { instance_double(Jira::ProjectConnection, call: project_hash) }
before do
# Ensure new proj conns always return mocked 'project' obj
allow(Jira::ProjectConnection).to receive(:new).with(
described_class::PROJECT_KEY
) { project }
end
If it still doesn't work, try temporarily replacing described_class::PROJECT_KEY with anything to debug; this can help you confirm if you specified the wrong arg(s) being sent to new.
With regard to the error message, it looks like JIRA::Resource::Issue doesn't have a fields attribute/method, though fields appears to be nested in attrs? The JIRA::Resource::Project#issues method also translates the issues in the JSON into Issue objects, so if you're using that method you will need to change the contents of project_hash.

Using variable declared in one method to open webpage in another method

I am working on a CLI Project and trying to open up a web page by using url variable declared in another method.
def self.open_deal_page(input)
index = input.to_i - 1
#deals = PopularDeals::NewDeals.new_deals
#deals.each do |info|
d = info[index]
#product_url = "#{d.url}"
end
#product_url.to_s
puts "They got me!"
end
def self.deal_page(product_url)
#self.open_deal_page(input)
deal = {}
html = Nokogiri::HTML(open(#product_url))
doc = Nokogiri::HTML(html)
deal[:name] = doc.css(".dealTitle h1").text.strip
deal[:discription] = doc.css(".textDescription").text.strip
deal[:purchase] = doc.css("div a.button").attribute("href")
deal
#binding.pry
end
but I am receiving this error.
`open': no implicit conversion of nil into String (TypeError)
any possible solution? Thank you so much in advance.
Try returning your #product_url within your open_deal_page method, because now you're returning puts "They got me!", and also note that your product_url is being created inside your each block, so, it won't be accessible then, try creating it before as an empty string and then you can return it.
def open_deal_page(input)
...
# Create the variable
product_url = ''
# Assign it the value
deals.each do |info|
product_url = "#{info[index].url}"
end
# And return it
product_url
end
In your deal_page method tell to Nokogiri to open the product_url that you're passing as argument.
def deal_page(product_url)
...
html = Nokogiri::HTML(open(product_url))
...
end

In Rails, why are my controller params being modified by the class

I have a controller action somewhat similar to this:
def reports
puts params
#stats = Client.stats(params)
puts params
end
The initial params might look like this:
{ end: '2012-01-01 21:00:19' }
And in my Client model, I have this:
def self.stats(opts)
opts[:start] = (Time.now - 30.days).to_i
...do some calculations..
return stats
end
If I inspect the params object that was sent before and after the function runs, I can see it's been modified by the self.stats method.
In the example above, I'm not sending 'start' in the initial params, the method adds it for the calculations - as expected.
What I was not expecting was that the function would modify the original hash!
Can someone explain why this is happening?
--EDIT--
I forgot to say I tried to create a copy of the params and use that instead, same issue.
def reports
a = params
#stats = Client.stats(a)
puts params
end
The params are still updated?!
That's, because your function call gets a reference to the params not a copy. If you do something like opts[:start] = (Time.now - 30.days).to_i you are editing the params object.
a = params: now both variables point to the same place in the memory. You copied the pointer only.
Google for ruby object copy or ruby deep copy or search at stackoverflow for it. At a first try you could try params.clone.
Whenever you are updating any value of params, take a copy of params like this
a = params.clone
It will create a new element in memory
if you do like this it wont create a new element in memory it will point the same memory
a = params
Try this

Assignment method created using define_singleton_method returns the wrong value

Background
The Entity class is a base class that gets inherited by several subclasses that holds entities received over a REST API. The entity classes are immutable and should return a new instance of themselves whenever a change is attempted.
The Entity class has an .update() method that takes a hash of values to update, if the changes aren't really changes it returns itself and if there are real changes it returns a new instance of itself with the changes effected before instantiation.
To be user friendly Entity also allows for direct assignment to properties (so that if a subclass of Entity has a name attribute you can do instance.name = 'New Name') that also returns a new instance of the class. This is implemented in terms of update using dynamic methods that are created when the class is instantiated.
And they are the problem.
Problem
The code in the Entity class looks, in part, like this (for a complete code listing and tests check out the Github repo: https://github.com/my-codeworks/fortnox-api.git):
require "virtus"
require "ice_nine"
class Entity
extend Forwardable
include Virtus.model
def initialize( hash = {} )
super
create_attribute_setter_methods
IceNine.deep_freeze( self )
end
def update( hash )
attributes = self.to_hash.merge( hash )
return self if attributes == self.to_hash
self.class.new( attributes )
end
private
def create_attribute_setter_methods
attribute_set.each do |attribute|
name = attribute.options[ :name ]
create_attribute_setter_method( name )
end
end
def create_attribute_setter_method( name )
self.define_singleton_method "#{name}=" do | value |
self.update( name => value )
end
end
end
Doing this:
instance.update( name: 'New Name' )
and this:
instance.name = 'New Name'
Should be the same, literally since one is implemented in terms of the other.
While .update() works perfectly the .attr=() methods return the value you assign.
So in the above example .update() returns a new instance of the Entity subclass but .attr=() returns 'New Name' ...
I have tries capturing the output inside the .attr=() method and log it before returning so that I have this:
self.define_singleton_method "#{name}=" do | value |
p "Called as :#{name}=, redirecting to update( #{name}: #{value} )"
r = self.update( name => value )
p "Got #{r} back from update"
return r
end
And the log lines say:
"Called as :name=, redirecting to update( name: 'New Name' )"
"Got #<TestEntity:0x007ffedbd0ad18> back from update"
But all I get is the string 'New Name'...
My forehead is bloody and no posts I find show anything close to this. I bet I'm doing something wrong but I can't find it.
Getting dirty
The Github repo has tests in rspec that you can run, the failing ones are focused right now and some extra logging is in the Entity class to capture the different internal steps.
Comments, links and/or pull requests are welcome.
Turns out that the = methods always return the value being assigned.
o = Struct.new(:key).new(1)
o.define_singleton_method("something") { #something }
o.define_singleton_method("something=") do |v|
#something = v
return 6
end
As you can see, I've 'fixed' the return value to 6 each time something= is called. Let's see if it works:
o.something = 1 #=> outputs 1, not 6
o.something #=> outputs 1, so the method did indeed run
Conclusion? My guess is that an = method will return the value that you are assigning through it. And IMO it's better this way; one reason would be to ensure proper functioning of assignment chains:
new_val = o.something = some_val

why would .is_a? and .class give conflicting results?

I have three objects that are all the same class. One was created via Item.new and the other two were pulled from the database (Mongoid). I'm passing one/any of these objects to another method and checking the type in that method via is_a?:
def initialize (item, attrs = nil, options = nil)
super(attrs, options)
raise 'invalid item object' unless item.is_a?(Item)
Well, this raise is getting hit. So I check the class, is_a and instance_of in rails console. I'm getting conflicting results. Why would they have the same class but only one of them be an instance_of that class?
>> i0.is_a? Item
=> false
>> i1.is_a? Item
=> false
>> i2.is_a? Item
=> true
>> i0.class
=> Item
>> i1.class
=> Item
>> i2.class
=> Item
>> i0.instance_of?(Item)
=> false
>> i1.instance_of?(Item)
=> false
>> i2.instance_of?(Item)
=> true
Is there a better way to do this type checking of my inputs? Why would three things that are the same class not all be instances of that class?
I don't know Mongoid, but usually, in a DB access library, you don't get the actual object out of the database but rather a proxy object that acts as a stand-in for the object stored in the DB. Since Ruby lacks the features to implement a perfect transparent proxy, you will sometimes see odd results, especially when using reflection or around object identity.
Inspired on the #KL-7 comment, it must be happening sort of that:
class Item; end
class PseudoItem; end
# PseudodItem think it's an Item:
class << PseudoItem
def inspect
'Item'
end
end
i0 = Item.new
i1 = PseudoItem.new
i0.class #=> Item (correct!)
i1.class #=> Item (wrong, due to redefinition of inspect!)
i0.is_a? Item #=> true
i1.is_a? Item #=> false, as it is a PseudoItem
Ya, same problem here...
Problem resolved (bypassed) with am ugly:
i0.class.to_s==Item.to_s

Resources