From 1c37d8571275ecedf3d9858e3ff994058324f714 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 19 Sep 2014 22:36:47 +0200 Subject: [PATCH 1/8] Much simpler way to implement required UUIDs. Just remove the methods/properties/constants every time it's called again. Thanks @subvertallchris ! --- lib/neo4j/active_node.rb | 1 + lib/neo4j/active_node/id_property.rb | 36 +++++++++++++++++++---- lib/neo4j/active_node/labels.rb | 9 +++++- lib/neo4j/active_node/orm_adapter.rb | 2 +- lib/neo4j/active_node/persistence.rb | 3 +- lib/neo4j/shared/property.rb | 10 +++++++ spec/e2e/active_model_spec.rb | 6 ++-- spec/e2e/id_property_spec.rb | 8 +++-- spec/e2e/label_spec.rb | 10 +++---- spec/e2e/transaction_spec.rb | 11 +++++-- spec/integration/node_persistence_spec.rb | 17 ++++++----- spec/shared_examples/destroyable_model.rb | 4 +-- spec/shared_examples/saveable_model.rb | 2 +- 13 files changed, 87 insertions(+), 32 deletions(-) diff --git a/lib/neo4j/active_node.rb b/lib/neo4j/active_node.rb index 5054e0aaa..ab7fafb34 100644 --- a/lib/neo4j/active_node.rb +++ b/lib/neo4j/active_node.rb @@ -45,6 +45,7 @@ def neo4j_obj end included do + id_property :uuid, auto: :uuid def self.inherited(other) inherit_id_property(other) if self.has_id_property? inherited_indexes(other) if self.respond_to?(:indexed_properties) diff --git a/lib/neo4j/active_node/id_property.rb b/lib/neo4j/active_node/id_property.rb index 9457f3d64..03f485102 100644 --- a/lib/neo4j/active_node/id_property.rb +++ b/lib/neo4j/active_node/id_property.rb @@ -51,18 +51,24 @@ def validate_conf(conf) end def define_property_method(clazz, name) + clear_methods(clazz, name) + clazz.module_eval(%Q{ def id persisted? ? #{name} : nil end - property :#{name} validates_uniqueness_of :#{name} + + property :#{name} }, __FILE__, __LINE__) + end def define_uuid_method(clazz, name) + clear_methods(clazz, name) + clazz.module_eval(%Q{ default_property :#{name} do ::SecureRandom.uuid @@ -77,6 +83,8 @@ def #{name} end def define_custom_method(clazz, name, on) + clear_methods(clazz, name) + clazz.module_eval(%Q{ default_property :#{name} do |instance| raise "Specifying custom id_property #{name} on none existing method #{on}" unless instance.respond_to?(:#{on}) @@ -91,6 +99,20 @@ def #{name} }, __FILE__, __LINE__) end + def clear_methods(clazz, name) + if clazz.method_defined?(name) + clazz.module_eval(%Q{ + undef_method :#{name} + }, __FILE__, __LINE__) + end + + if clazz.attribute_names.include?(name.to_s) + clazz.module_eval(%Q{ + undef_property :#{name} + }, __FILE__, __LINE__) + end + end + extend self end @@ -98,10 +120,12 @@ def #{name} module ClassMethods def find_by_id(key) - Neo4j::Node.load(key.to_i) + self.where(id_property_name => key).first end def id_property(name, conf = {}) + drop_constraint(id_property_name, type: :unique) if @id_property_info + @id_property_info = {name: name, type: conf} TypeMethods.define_id_methods(self, name, conf) constraint name, type: :unique @@ -119,11 +143,13 @@ def id_property_info @id_property_info ||= {} end - def primary_key - id_property_info[:name] || :id + def id_property_name + id_property_info[:name] end + alias_method :primary_key, :id_property_name + end end -end \ No newline at end of file +end diff --git a/lib/neo4j/active_node/labels.rb b/lib/neo4j/active_node/labels.rb index 0087bd9f6..11616319f 100644 --- a/lib/neo4j/active_node/labels.rb +++ b/lib/neo4j/active_node/labels.rb @@ -129,12 +129,19 @@ def index(property, conf = {}) # Person.constraint :name, type: :unique # def constraint(property, constraints, session = Neo4j::Session.current) - Neo4j::Session.on_session_available do |_| + Neo4j::Session.on_session_available do |session| label = Neo4j::Label.create(mapped_label_name) label.create_constraint(property, constraints, session) end end + def drop_constraint(property, constraint, session = Neo4j::Session.current) + Neo4j::Session.on_session_available do |session| + label = Neo4j::Label.create(mapped_label_name) + label.drop_constraint(property, constraint, session) + end + end + def index?(index_def) mapped_label.indexes[:property_keys].include?([index_def]) end diff --git a/lib/neo4j/active_node/orm_adapter.rb b/lib/neo4j/active_node/orm_adapter.rb index 06c63950c..8a0da4d20 100644 --- a/lib/neo4j/active_node/orm_adapter.rb +++ b/lib/neo4j/active_node/orm_adapter.rb @@ -73,7 +73,7 @@ def hasherize_order(order) def extract_id!(conditions) if id = conditions.delete(:id) - conditions[:neo_id] = id.to_i + conditions[klass.id_property_name.to_sym] = id end end diff --git a/lib/neo4j/active_node/persistence.rb b/lib/neo4j/active_node/persistence.rb index bb04ffc53..7a44cb21d 100644 --- a/lib/neo4j/active_node/persistence.rb +++ b/lib/neo4j/active_node/persistence.rb @@ -98,9 +98,10 @@ def create!(*args) def load_entity(id) Neo4j::Node.load(id) end + end private end -end \ No newline at end of file +end diff --git a/lib/neo4j/shared/property.rb b/lib/neo4j/shared/property.rb index dee48dc32..c3a8dcee1 100644 --- a/lib/neo4j/shared/property.rb +++ b/lib/neo4j/shared/property.rb @@ -153,6 +153,16 @@ def property(name, options={}) constraint_or_index(name, options) end + def undef_property(name) + raise ArgumentError, "Argument `#{name}` not an attribute" if not attribute_names.include?(name.to_s) + + attribute_methods(name).each do |method| + undef_method(method) + end + + undef_constraint_or_index(name) + end + def default_property(name, &block) default_properties[name] = block end diff --git a/spec/e2e/active_model_spec.rb b/spec/e2e/active_model_spec.rb index 78ec4bc6c..782cd9d69 100644 --- a/spec/e2e/active_model_spec.rb +++ b/spec/e2e/active_model_spec.rb @@ -233,7 +233,7 @@ class Company before do Neo4j::Config[:cache_class_names] = true @cached = CacheTest.create - @unwrapped = Neo4j::Node._load(@cached.id) + @unwrapped = Neo4j::Node._load(@cached.neo_id) end it 'responds true to :cached_class?' do @@ -253,7 +253,7 @@ class Company before do Neo4j::Config[:cache_class_names] = false @uncached = CacheTest.create - @unwrapped = Neo4j::Node._load(@uncached.id) + @unwrapped = Neo4j::Node._load(@uncached.neo_id) end before { Neo4j::Config[:cache_class_names] = false } @@ -830,4 +830,4 @@ class RelClass expect(reflection.rel_class_name).to eq rel_clazz.name end end -end \ No newline at end of file +end diff --git a/spec/e2e/id_property_spec.rb b/spec/e2e/id_property_spec.rb index baef6bd25..6c28da7a8 100644 --- a/spec/e2e/id_property_spec.rb +++ b/spec/e2e/id_property_spec.rb @@ -42,10 +42,11 @@ end it 'uses the neo_id as id after save' do + SecureRandom.stub(:uuid) { 'secure123' } node = clazz.new expect(node.id).to eq(nil) node.save! - expect(node.id).to eq(node.neo_id) + expect(node.id).to eq('secure123') end it 'can find by id uses the neo_id' do @@ -55,11 +56,11 @@ end it 'returns :id as primary_key' do - expect(clazz.primary_key).to eq :id + expect(clazz.primary_key).to eq :uuid end it 'responds false to has_id_property' do - expect(clazz.has_id_property?).to be_falsey + expect(clazz.has_id_property?).to be_truthy end describe 'when having a configuration' do @@ -295,6 +296,7 @@ class Car < Vehicle; end Fruit = UniqueClass.create do include Neo4j::ActiveNode + id_property :my_id end diff --git a/spec/e2e/label_spec.rb b/spec/e2e/label_spec.rb index f9f82c948..e9d08a774 100644 --- a/spec/e2e/label_spec.rb +++ b/spec/e2e/label_spec.rb @@ -124,12 +124,12 @@ end it 'creates an index' do - expect(clazz.mapped_label.indexes).to eq(:property_keys => [[:name]]) + expect(clazz.mapped_label.indexes).to eq(:property_keys => [[:name], [:uuid]]) end it 'does not create index on other classes' do - expect(clazz.mapped_label.indexes).to eq(:property_keys => [[:name]]) - expect(other_class.mapped_label.indexes).to eq(:property_keys => []) + expect(clazz.mapped_label.indexes).to eq(:property_keys => [[:name], [:uuid]]) + expect(other_class.mapped_label.indexes).to eq(:property_keys => [[:uuid]]) end describe 'when inherited' do @@ -141,8 +141,8 @@ class Foo1 class Foo2 < Foo1 end - expect(Foo1.mapped_label.indexes).to eq(:property_keys => [[:name]]) - expect(Foo2.mapped_label.indexes).to eq(:property_keys => [[:name]]) + expect(Foo1.mapped_label.indexes).to eq(:property_keys => [[:name], [:uuid]]) + expect(Foo2.mapped_label.indexes).to eq(:property_keys => [[:name], [:uuid]]) end end diff --git a/spec/e2e/transaction_spec.rb b/spec/e2e/transaction_spec.rb index 263ec3fa3..844f78ea2 100644 --- a/spec/e2e/transaction_spec.rb +++ b/spec/e2e/transaction_spec.rb @@ -12,18 +12,25 @@ #:nocov: it 'returns hash values inside but outside it has the node value after commit' do + i = 0 + SecureRandom.stub(:uuid) do + i += 1 + "secure1234_#{i}" + end if Neo4j::Session.current.db_type == :server_db + clazz tx = Neo4j::Transaction.new a = clazz.create name: 'a' b = clazz.create name: 'b' a.thing = b - expect(a.thing).to eq("name"=>"b", "_classname"=>clazz.to_s) + expect(a.thing).to eq("name"=>"b", "_classname"=>clazz.to_s, "uuid" => "secure1234_2") tx.close expect(a.thing).to eq(b) end if Neo4j::Session.current.db_type == :embedded_db + clazz tx = Neo4j::Transaction.new a = clazz.create name: 'a' b = clazz.create name: 'b' @@ -36,4 +43,4 @@ end #:nocov: end -end \ No newline at end of file +end diff --git a/spec/integration/node_persistence_spec.rb b/spec/integration/node_persistence_spec.rb index e34c2c806..c0e0b9ac7 100644 --- a/spec/integration/node_persistence_spec.rb +++ b/spec/integration/node_persistence_spec.rb @@ -11,6 +11,7 @@ class MyThing before do + SecureRandom.stub(:uuid) { 'secure123' } @session = double("Mock Session", create_node: nil) MyThing.stub(:cached_class?).and_return(false) Neo4j::Session.stub(:current).and_return(@session) @@ -30,14 +31,14 @@ class MyThing describe "create" do it "does not store nil values" do node = double('unwrapped_node', props: {a: 999}) - @session.should_receive(:create_node).with({a: 1}, [:MyThing]).and_return(node) + @session.should_receive(:create_node).with({a: 1, uuid: 'secure123'}, [:MyThing]).and_return(node) thing = MyThing.create(a: 1) thing.props.should == {a: 999} end it 'stores undefined attributes' do node = double('unwrapped_node', props: {a: 999}) - @session.should_receive(:create_node).with({a: 1}, [:MyThing]).and_return(node) + @session.should_receive(:create_node).with({a: 1, uuid: 'secure123'}, [:MyThing]).and_return(node) thing = MyThing.create(a: 1) thing.attributes.should == {"a" => 999, "x" => nil} # always reads the result from the database end @@ -86,7 +87,7 @@ class MyThing let(:node) { double('unwrapped_node', props: {a: 3}) } it 'saves declared the properties that has been changed with []= operator' do - @session.should_receive(:create_node).with({x: 42}, [:MyThing]).and_return(node) + @session.should_receive(:create_node).with({x: 42, uuid: 'secure123'}, [:MyThing]).and_return(node) thing = MyThing.new thing[:x] = 42 thing.save @@ -104,7 +105,7 @@ class MyThing let(:node) { double('unwrapped_node', props: {a: 3}) } it 'does not save unchanged properties' do - @session.should_receive(:create_node).with({a: 'foo', x: 44}, [:MyThing]).and_return(node) + @session.should_receive(:create_node).with({a: 'foo', x: 44, uuid: 'secure123'}, [:MyThing]).and_return(node) thing = MyThing.create(a: 'foo', x: 44) # only change X @@ -114,7 +115,7 @@ class MyThing end it 'handles nil properties' do - @session.should_receive(:create_node).with({a: 'foo', x: 44}, [:MyThing]).and_return(node) + @session.should_receive(:create_node).with({a: 'foo', x: 44, uuid: 'secure123'}, [:MyThing]).and_return(node) thing = MyThing.create(a: 'foo', x: 44) node.should_receive(:update_props).with('x' => nil) @@ -131,7 +132,7 @@ class MyThing end it 'updates given property' do - expect(@session).to receive(:create_node).with({a:42}, [:MyThing]).and_return(node) + expect(@session).to receive(:create_node).with({a:42, uuid: 'secure123'}, [:MyThing]).and_return(node) thing.update(a: 42) end @@ -150,7 +151,7 @@ class MyThing end it 'updates given properties' do - expect(@session).to receive(:create_node).with({a:42, x: 'hej'}, [:MyThing]).and_return(node) + expect(@session).to receive(:create_node).with({a:42, x: 'hej', uuid: 'secure123'}, [:MyThing]).and_return(node) thing.update_attributes(a: 42, x: 'hej') end @@ -169,7 +170,7 @@ class MyThing end it 'updates given property' do - expect(@session).to receive(:create_node).with({a:42}, [:MyThing]).and_return(node) + expect(@session).to receive(:create_node).with({a:42, uuid: 'secure123'}, [:MyThing]).and_return(node) thing.update_attribute!(:a, 42) end diff --git a/spec/shared_examples/destroyable_model.rb b/spec/shared_examples/destroyable_model.rb index b721f3d4b..d6200be11 100644 --- a/spec/shared_examples/destroyable_model.rb +++ b/spec/shared_examples/destroyable_model.rb @@ -2,14 +2,14 @@ context "when saved" do before :each do subject.save! - @other = subject.class.load_entity(subject.id) + @other = subject.class.find_by_id(subject.id) @old_id = subject.id subject.destroy end it { should be_frozen } it "should remove the model from the database" do - subject.class.load_entity(@old_id).should be_nil + subject.class.find_by_id(@old_id).should be_nil end it "should also be frozen in @other" do diff --git a/spec/shared_examples/saveable_model.rb b/spec/shared_examples/saveable_model.rb index e8c4fd0cf..e5e1e7a7f 100644 --- a/spec/shared_examples/saveable_model.rb +++ b/spec/shared_examples/saveable_model.rb @@ -30,7 +30,7 @@ end it { should be_persisted } - it { should == subject.class.load_entity(subject.id) } + it { should == subject.class.find_by_id(subject.id) } it { should be_valid } it "should be found in the database" do From 8ef59abe80791798e5175c9389d9e084c264215d Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 09:39:06 +0200 Subject: [PATCH 2/8] Make sure this happens when there's a session --- lib/neo4j/active_node.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/neo4j/active_node.rb b/lib/neo4j/active_node.rb index ab7fafb34..336831794 100644 --- a/lib/neo4j/active_node.rb +++ b/lib/neo4j/active_node.rb @@ -45,7 +45,6 @@ def neo4j_obj end included do - id_property :uuid, auto: :uuid def self.inherited(other) inherit_id_property(other) if self.has_id_property? inherited_indexes(other) if self.respond_to?(:indexed_properties) @@ -68,6 +67,8 @@ def self.inherit_id_property(other) end Neo4j::Session.on_session_available do |_| + id_property :uuid, auto: :uuid + name = Neo4j::Config[:id_property] type = Neo4j::Config[:id_property_type] value = Neo4j::Config[:id_property_type_value] From 1393f4094a4e06197fe72dfcaf98d7e90a356acf Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 09:39:57 +0200 Subject: [PATCH 3/8] Rescue from issues when dropping a constraint. In my DB with 8 million nodes for a particular label, adding a constraint takes 141 seconds and it seems to be asynchronous for some reason... --- lib/neo4j/active_node/id_property.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/neo4j/active_node/id_property.rb b/lib/neo4j/active_node/id_property.rb index 03f485102..48c0f7be8 100644 --- a/lib/neo4j/active_node/id_property.rb +++ b/lib/neo4j/active_node/id_property.rb @@ -124,7 +124,10 @@ def find_by_id(key) end def id_property(name, conf = {}) - drop_constraint(id_property_name, type: :unique) if @id_property_info + begin + drop_constraint(id_property_name, type: :unique) if has_id_property? + rescue Neo4j::Server::CypherResponse::ResponseError + end @id_property_info = {name: name, type: conf} TypeMethods.define_id_methods(self, name, conf) @@ -136,7 +139,7 @@ def id_property(name, conf = {}) end def has_id_property? - !id_property_info.empty? + id_property_info && !id_property_info.empty? end def id_property_info From fa7190785875a1506623e6fec484d7616dd7b1ac Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 09:40:20 +0200 Subject: [PATCH 4/8] Use UUIDs in query/query_proxy methods --- lib/neo4j/active_node/query/query_proxy_methods.rb | 4 ++-- lib/neo4j/active_node/query_methods.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/neo4j/active_node/query/query_proxy_methods.rb b/lib/neo4j/active_node/query/query_proxy_methods.rb index a8aa00529..b4bd8414a 100644 --- a/lib/neo4j/active_node/query/query_proxy_methods.rb +++ b/lib/neo4j/active_node/query/query_proxy_methods.rb @@ -37,7 +37,7 @@ def empty?(target=nil) def include?(other, target=nil) raise(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id) query_with_target(target) do |target| - self.where("ID(#{target}) = {other_node_id}").params(other_node_id: other.neo_id).query.return("count(#{target}) AS count").first.count > 0 + self.query_as(target).where(target => {@model.primary_key => other.id}).return("count(#{target}) AS count").first.count > 0 end end @@ -69,4 +69,4 @@ def exists_query_start(origin, condition, target) end end end -end \ No newline at end of file +end diff --git a/lib/neo4j/active_node/query_methods.rb b/lib/neo4j/active_node/query_methods.rb index a2867a7d4..b12fca11b 100644 --- a/lib/neo4j/active_node/query_methods.rb +++ b/lib/neo4j/active_node/query_methods.rb @@ -12,12 +12,12 @@ def exists?(node_condition=nil) # Returns the first node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs. def first - self.query_as(:n).limit(1).order('ID(n)').pluck(:n).first + self.query_as(:n).limit(1).order(n: primary_key).pluck(:n).first end # Returns the last node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs. def last - self.query_as(:n).limit(1).order('ID(n) DESC').pluck(:n).first + self.query_as(:n).limit(1).order(n: {primary_key => :desc}).pluck(:n).first end # @return [Fixnum] number of nodes of this class @@ -38,7 +38,7 @@ def empty? def include?(other) raise(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id) - self.query_as(:n).where("ID(n) = #{other.neo_id}").return("count(n) AS count").first.count > 0 + self.query_as(:n).where(n: {primary_key => other.id}).return("count(n) AS count").first.count > 0 end private @@ -55,4 +55,4 @@ def exists_query_start(node_condition) end end end -end \ No newline at end of file +end From 7c6acf6508c5e6104a2e0a73bc5860f8c4c38c77 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 09:51:46 +0200 Subject: [PATCH 5/8] Whoops, conflated variables here --- lib/neo4j/active_node/query/query_proxy_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/neo4j/active_node/query/query_proxy_methods.rb b/lib/neo4j/active_node/query/query_proxy_methods.rb index b4bd8414a..65956e946 100644 --- a/lib/neo4j/active_node/query/query_proxy_methods.rb +++ b/lib/neo4j/active_node/query/query_proxy_methods.rb @@ -37,7 +37,7 @@ def empty?(target=nil) def include?(other, target=nil) raise(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id) query_with_target(target) do |target| - self.query_as(target).where(target => {@model.primary_key => other.id}).return("count(#{target}) AS count").first.count > 0 + self.query.where(target => {@model.primary_key => other.id}).return("count(#{target}) AS count").first.count > 0 end end From de559ba73688c3033909e5364466a3b41e45de89 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 10:01:26 +0200 Subject: [PATCH 6/8] UUIDs aren't created as sorted --- spec/integration/label_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integration/label_spec.rb b/spec/integration/label_spec.rb index ce0119305..a313e8b33 100644 --- a/spec/integration/label_spec.rb +++ b/spec/integration/label_spec.rb @@ -178,13 +178,13 @@ class EmptyTestClass describe 'first' do it 'returns the first object created... sort of, see docs' do - expect(FirstLastTestClass.first).to eq @jasmine + expect(FirstLastTestClass.first).to eq [@jasmine, @lauren].sort_by(&:uuid).first end end describe 'last' do it 'returns the last object created... sort of, see docs' do - expect(FirstLastTestClass.last).to eq @lauren + expect(FirstLastTestClass.last).to eq [@jasmine, @lauren].sort_by(&:uuid).last end it 'returns nil when there are no results' do From 43c5f0b37b92e39789047c68813ad4b93fff2901 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 10:10:53 +0200 Subject: [PATCH 7/8] Cover undef_property --- spec/unit/shared/property_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/unit/shared/property_spec.rb b/spec/unit/shared/property_spec.rb index 80256552c..213d24863 100644 --- a/spec/unit/shared/property_spec.rb +++ b/spec/unit/shared/property_spec.rb @@ -9,4 +9,18 @@ expect{clazz.property :foo}.to raise_error(Neo4j::Shared::Property::IllegalPropertyError) end end + + describe '.undef_property' do + before(:each) do + clazz.property :bar + + expect(clazz).to receive(:undef_constraint_or_index) + clazz.undef_property :bar + end + it 'removes methods' do + clazz.method_defined?(:bar).should be false + clazz.method_defined?(:bar=).should be false + clazz.method_defined?(:bar?).should be false + end + end end From cb10870e826b4c571eae2e439f9f2eeb0add972e Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Sun, 21 Sep 2014 10:19:18 +0200 Subject: [PATCH 8/8] Need to include the middle node so that the sorts can compare properly --- spec/integration/label_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/integration/label_spec.rb b/spec/integration/label_spec.rb index a313e8b33..84116a2f8 100644 --- a/spec/integration/label_spec.rb +++ b/spec/integration/label_spec.rb @@ -172,19 +172,19 @@ class EmptyTestClass end @jasmine = FirstLastTestClass.create(name: 'jasmine') - FirstLastTestClass.create + @middle = FirstLastTestClass.create @lauren = FirstLastTestClass.create(name: 'lauren') end describe 'first' do it 'returns the first object created... sort of, see docs' do - expect(FirstLastTestClass.first).to eq [@jasmine, @lauren].sort_by(&:uuid).first + expect(FirstLastTestClass.first).to eq [@jasmine, @middle, @lauren].sort_by(&:uuid).first end end describe 'last' do it 'returns the last object created... sort of, see docs' do - expect(FirstLastTestClass.last).to eq [@jasmine, @lauren].sort_by(&:uuid).last + expect(FirstLastTestClass.last).to eq [@jasmine, @middle, @lauren].sort_by(&:uuid).last end it 'returns nil when there are no results' do