diff --git a/README.md b/README.md index abad3376..8eb825c8 100644 --- a/README.md +++ b/README.md @@ -211,11 +211,11 @@ Country#name= # => sets the name The ActiveHash::Base.all method functions like an in-memory data store. You can save your records as ActiveHash::Relation object by using standard ActiveRecord create and save methods: ```ruby Country.all -=> # +=> # Country.create => #1}> Country.all -=> #1}>], @query_hash={}, @records_dirty=false> +=> #1}>], @query_list=[], @records_dirty=false> country = Country.new => # country.new_record? @@ -225,7 +225,7 @@ country.save country.new_record? # => false Country.all -=> #1}>, #2}>], @query_hash={}, @records_dirty=false> +=> #1}>, #2}>], @query_list=[], @records_dirty=false> ``` Notice that when adding records to the collection, it will auto-increment the id for you by default. If you use string ids, it will not auto-increment the id. Available methods are: ``` diff --git a/lib/active_hash/base.rb b/lib/active_hash/base.rb index a48294df..515394d3 100644 --- a/lib/active_hash/base.rb +++ b/lib/active_hash/base.rb @@ -43,7 +43,7 @@ def not(options) options.present? && match_options?(record, options) end - ActiveHash::Relation.new(@scope.klass, filtered_records, {}) + ActiveHash::Relation.new(@scope.klass, filtered_records, []) end def match_options?(record, options) @@ -205,7 +205,7 @@ def create!(attributes = {}) end def all(options = {}) - ActiveHash::Relation.new(self, @records || [], options[:conditions] || {}) + ActiveHash::Relation.new(self, @records || [], options[:conditions].to_a) end delegate :where, :find, :find_by, :find_by!, :find_by_id, :count, :pluck, :ids, :pick, :first, :last, :order, to: :all diff --git a/lib/active_hash/relation.rb b/lib/active_hash/relation.rb index 725a21c4..8d37799c 100644 --- a/lib/active_hash/relation.rb +++ b/lib/active_hash/relation.rb @@ -7,10 +7,10 @@ class Relation delegate :empty?, :length, :first, :second, :third, :last, to: :records delegate :sample, to: :records - def initialize(klass, all_records, query_hash = nil) + def initialize(klass, all_records, query_list = nil) self.klass = klass self.all_records = all_records - self.query_hash = query_hash + self.query_list = query_list self.records_dirty = false self end @@ -18,8 +18,11 @@ def initialize(klass, all_records, query_hash = nil) def where(query_hash = :chain) return ActiveHash::Base::WhereChain.new(self) if query_hash == :chain - self.records_dirty = true unless query_hash.nil? || query_hash.keys.empty? - self.query_hash.merge!(query_hash || {}) + unless query_hash.blank? + self.records_dirty = true + self.query_list.concat(query_hash.to_a) + end + self end @@ -58,10 +61,10 @@ def find(id = nil, *args, &block) end def find_by_id(id) - return where(id: id).first if query_hash.present? + return where(id: id).first if query_list.present? index = klass.send(:record_index)[id.to_s] # TODO: Make index in Base publicly readable instead of using send? - index and records[index] + index and all_records[index] end def count @@ -94,7 +97,7 @@ def pick(*column_names) end def reload - @records = filter_all_records_by_query_hash + @records = filter_all_records_by_query_list end def order(*options) @@ -120,11 +123,11 @@ def method_missing(method_name, *args) instance_exec(*args, &self.klass.scopes[method_name]) end - attr_reader :query_hash, :klass, :all_records, :records_dirty + attr_reader :query_list, :klass, :all_records, :records_dirty private - attr_writer :query_hash, :klass, :all_records, :records_dirty + attr_writer :query_list, :klass, :all_records, :records_dirty def records if !defined?(@records) || @records.nil? || records_dirty @@ -134,21 +137,23 @@ def records end end - def filter_all_records_by_query_hash + def filter_all_records_by_query_list self.records_dirty = false - return all_records if query_hash.blank? + return all_records if query_list.blank? # use index if searching by id - if query_hash.key?(:id) || query_hash.key?("id") - ids = (query_hash.delete(:id) || query_hash.delete("id")) + query_by_id_list = query_list.select { |option| option.first == :id || option.first == "id" } + candidates = query_by_id_list.reduce(nil) do |candidates, option| + ids = query_list.delete(option).last ids = range_to_array(ids) if ids.is_a?(Range) - candidates = Array.wrap(ids).map { |id| klass.find_by_id(id) }.compact + new_candidates = Array.wrap(ids).map { |id| klass.find_by_id(id) }.compact + + candidates.nil? ? new_candidates : candidates & new_candidates end - - return candidates if query_hash.blank? + return candidates if query_list.blank? (candidates || all_records || []).select do |record| - match_options?(record, query_hash) + match_options?(record, query_list) end end diff --git a/spec/active_hash/base_spec.rb b/spec/active_hash/base_spec.rb index 9b06f930..1697acdc 100644 --- a/spec/active_hash/base_spec.rb +++ b/spec/active_hash/base_spec.rb @@ -307,15 +307,20 @@ class Region < ActiveHash::Base expect(Country.where(:name => [:US, :Canada]).map(&:name)).to match_array(%w(US Canada)) end - it 'is chainable' do - where_relation = Country.where(language: 'English') + it "is chainable" do + where_relation = Country.where(language: "English") expect(where_relation.length).to eq 2 expect(where_relation.map(&:id)).to eq([1, 2]) - chained_where_relation = where_relation.where(name: 'US') + chained_where_relation = where_relation.where(name: "US") expect(chained_where_relation.length).to eq 1 expect(chained_where_relation.map(&:id)).to eq([1]) end + + it "is chainable with same attribute" do + expect(Country.where(id: 1..2).where(id: 2..3).pluck(:id)).to match_array([2]) + expect(Country.where(language: "English").where(language: "Spanish").length).to eq 0 + end end describe ".where.not" do @@ -410,6 +415,14 @@ class Region < ActiveHash::Base it "filters records for multiple conditions" do expect(Country.where.not(:id => 1, :name => 'Mexico')).to match_array([Country.find(2)]) end + + it "is chainable with where" do + expect(Country.where.not(name: "US").where(language: "English").pluck(:name)).to match_array(["Canada"]) + end + + it "is chainable with where for same attribute" do + expect(Country.where(id: 2..3).where(id: 1..3).where.not(id: 1..2).where.not(id: 1).pluck(:id)).to match_array([3]) + end end describe ".find_by" do