Skip to content

Commit

Permalink
Merge pull request #177 from Sean0628/add_order_method
Browse files Browse the repository at this point in the history
Add ActiveHash::Base.order method inspired by ActiveRecord
  • Loading branch information
syguer committed Oct 26, 2019
2 parents c55084d + 405cbfe commit 9ebc83d
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -170,6 +170,7 @@ Country.find_by(name: 'US') # => returns the first country object with specif
Country.find_by!(name: 'US') # => same as find_by, but raise exception when not found
Country.where(name: 'US') # => returns all records with name: 'US'
Country.where.not(name: 'US') # => returns all records without name: 'US'
Country.order(name: :desc) # => returns all records ordered by name attribute in DESC order
```
It also gives you a few dynamic finder methods. For example, if you defined :name as a field, you'd get:
```ruby
Expand Down
2 changes: 1 addition & 1 deletion lib/active_hash/base.rb
Expand Up @@ -188,7 +188,7 @@ def all(options = {})
ActiveHash::Relation.new(self, @records || [], options[:conditions] || {})
end

delegate :where, :find, :find_by, :find_by!, :find_by_id, :count, :pluck, :first, :last, to: :all
delegate :where, :find, :find_by, :find_by!, :find_by_id, :count, :pluck, :first, :last, :order, to: :all

def transaction
yield
Expand Down
51 changes: 50 additions & 1 deletion lib/active_hash/relation.rb
Expand Up @@ -3,7 +3,7 @@ class Relation
include Enumerable

delegate :each, to: :records # Make Enumerable work
delegate :equal?, :==, :===, :eql?, to: :records
delegate :equal?, :==, :===, :eql?, :sort!, to: :records
delegate :empty?, :length, :first, :second, :third, :last, to: :records

def initialize(klass, all_records, query_hash = nil)
Expand Down Expand Up @@ -72,11 +72,25 @@ def pluck(*column_names)
def reload
@records = filter_all_records_by_query_hash
end

def order(*options)
check_if_method_has_arguments!(:order, options)
relation = where({})
return relation if options.blank?

processed_args = preprocess_order_args(options)
candidates = relation.dup

order_by_args!(candidates, processed_args)

candidates
end

def to_ary
records.dup
end


attr_reader :query_hash, :klass, :all_records, :records_dirty

private
Expand Down Expand Up @@ -129,5 +143,40 @@ def range_to_array(range)
e = records.last[:id]
(range.begin..e).to_a
end

def check_if_method_has_arguments!(method_name, args)
return unless args.blank?

raise ArgumentError,
"The method .#{method_name}() must contain arguments."
end

def preprocess_order_args(order_args)
order_args.reject!(&:blank?)
return order_args.reverse! unless order_args.first.is_a?(String)

ary = order_args.first.split(', ')
ary.map! { |e| e.split(/\W+/) }.reverse!
end

def order_by_args!(candidates, args)
args.each do |arg|
field, dir = if arg.is_a?(Hash)
arg.to_a.flatten.map(&:to_sym)
elsif arg.is_a?(Array)
arg.map(&:to_sym)
else
arg.to_sym
end

candidates.sort! do |a, b|
if dir.present? && dir.to_sym.upcase.equal?(:DESC)
b[field] <=> a[field]
else
a[field] <=> b[field]
end
end
end
end
end
end
74 changes: 74 additions & 0 deletions spec/active_hash/base_spec.rb
Expand Up @@ -868,6 +868,80 @@ class Region < ActiveHash::Base
end
end

describe ".order" do
before do
Country.field :name
Country.field :language
Country.field :code
Country.data = [
{ id: 1, name: "US", language: "English", code: 1 },
{ id: 2, name: "Canada", language: "English", code: 1 },
{ id: 3, name: "Mexico", language: "Spanish", code: 52 }
]
end

it "raises ArgumentError if no args are provieded" do
expect { Country.order() }.to raise_error(ArgumentError, 'The method .order() must contain arguments.')
end

it "returns all records when passed nil" do
expect(Country.order(nil).to_a).to eq Country.all.to_a
end

it "returns all records when an empty hash" do
expect(Country.order({}).to_a).to eq Country.all.to_a
end

it "returns all records ordered by name attribute in ASC order when ':name' is provieded" do
countries = Country.order(:name)
expect(countries.first).to eq Country.find_by(name: "Canada")
expect(countries.second).to eq Country.find_by(name: "Mexico")
expect(countries.third).to eq Country.find_by(name: "US")
end

it "returns all records ordered by name attribute in DESC order when 'name: :desc' is provieded" do
countries = Country.order(name: :desc)
expect(countries.first).to eq Country.find_by(name: "US")
expect(countries.second).to eq Country.find_by(name: "Mexico")
expect(countries.third).to eq Country.find_by(name: "Canada")
end

it "returns all records ordered by code attribute, followed by id attribute in DESC order when ':code, id: :desc' is provieded" do
countries = Country.order(:code, id: :desc)
expect(countries.first).to eq Country.find_by(name: "Canada")
expect(countries.second).to eq Country.find_by(name: "US")
expect(countries.third).to eq Country.find_by(name: "Mexico")
end

it "returns all records ordered by name attribute in ASC order when 'name' is provieded" do
countries = Country.order("name")
expect(countries.first).to eq Country.find_by(name: "Canada")
expect(countries.second).to eq Country.find_by(name: "Mexico")
expect(countries.third).to eq Country.find_by(name: "US")
end

it "returns all records ordered by name attribute in DESC order when 'name: :desc' is provieded" do
countries = Country.order("name DESC")
expect(countries.first).to eq Country.find_by(name: "US")
expect(countries.second).to eq Country.find_by(name: "Mexico")
expect(countries.third).to eq Country.find_by(name: "Canada")
end

it "returns all records ordered by code attributes, followed by id attribute in DESC order when ':code, id: :desc' is provieded" do
countries = Country.order("code, id DESC")
expect(countries.first).to eq Country.find_by(name: "Canada")
expect(countries.second).to eq Country.find_by(name: "US")
expect(countries.third).to eq Country.find_by(name: "Mexico")
end

it "populates the data correctly in the order provided" do
countries = Country.where(language: 'English').order(id: :desc)
expect(countries.count).to eq 2
expect(countries.first).to eq Country.find_by(name: "Canada")
expect(countries.second).to eq Country.find_by(name: "US")
end
end

describe "#method_missing" do
it "doesn't blow up if you call a missing dynamic finder when fields haven't been set" do
proc do
Expand Down

0 comments on commit 9ebc83d

Please sign in to comment.