Is possible to order a nested model?

I have a menu, that is hierarchical and stored according to nested set
model. i want to do this to have each “level” ordered alphabetically,
like this:

-country
–Andorra
–Belgium
–Norway
–Sweden
-food
–English
–Swedish
–Thai

In the example Country and Food must be ordered alphabetically, inside
country those countries must be ordered alphabetically and the cuisines
within food must be ordered alphabetically.

Is that possible?

Hope for help!

marek wrote:

I have a menu, that is hierarchical and stored according to nested set
model. i want to do this to have each “level” ordered alphabetically,
like this:

-country
–Andorra
–Belgium
–Norway
–Sweden
-food
–English
–Swedish
–Thai

In the example Country and Food must be ordered alphabetically, inside
country those countries must be ordered alphabetically and the cuisines
within food must be ordered alphabetically.

Is that possible?

Hope for help!

It depends a bit on how you are storing the menu items.

If they are all in one multi-dimensional array, try this:

a = [[“4”,“3”,“2”,“1”],[“2”,“3”,“1”]]
=> [[“4”,“3”,“2”,“1”],[“2”,“3”,“1”]]
a.collect{|x| x.sort}
=> [[“1”,“2”,“3”,“4”],[“1”,“2”,“3”]]

Andrew S. wrote:

marek wrote:

I have a menu, that is hierarchical and stored according to nested set
model. i want to do this to have each “level” ordered alphabetically,
like this:

-country
–Andorra
–Belgium
–Norway
–Sweden
-food
–English
–Swedish
–Thai

In the example Country and Food must be ordered alphabetically, inside
country those countries must be ordered alphabetically and the cuisines
within food must be ordered alphabetically.

Is that possible?

Hope for help!

It depends a bit on how you are storing the menu items.

If they are all in one multi-dimensional array, try this:

a = [[“4”,“3”,“2”,“1”],[“2”,“3”,“1”]]
=> [[“4”,“3”,“2”,“1”],[“2”,“3”,“1”]]
a.collect{|x| x.sort}
=> [[“1”,“2”,“3”,“4”],[“1”,“2”,“3”]]

i’m storing it with create the category and then add it to a root or
parent category so i have for example

root
-country
–andorra
–norway
–belgium
-food
–thai
–english

i retrieve all them (to use with a select) with Category.find(:all,
:order => “lft”).map{|category| ["-" * category.depth + category.name,
category.id]}
and i have something like: [[“root”, 4], ["-contry", 5], ["–andorra",
7], ["–norway", 11], ["–belgium", 12], ["-food",14], ["–thai", 15],
["–english", 18]]
how can i get them, like your example, in a multidimensional array?

cover wrote:

and i have something like: [[“root”, 4], ["-contry", 5], ["–andorra",
7], ["–norway", 11], ["–belgium", 12], ["-food",14], ["–thai", 15],
["–english", 18]]
how can i get them, like your example, in a multidimensional array?

Your function is returning an array comprised of 2 dimensional arrays
(the formatted menu item and the id). This make it a little tricky to
sort it at this point so I would sort it at creation.

Get the database to do it for you:

Category.find(:all,
:order => “lft, depth, name”
).map{|category|
["-" * category.depth +
category.name,
category.id]}

Note: you will have to tweak this depending on the schema.

I would also push this to a method of the Model to clean up you
Controller/View and make it available to other Controllers.

Andrew S. wrote:

Get the database to do it for you:

Category.find(:all,
:order => “lft, depth, name”
).map{|category|
["-" * category.depth +
category.name,
category.id]}

The result it’s the same :frowning:
i think because it first order all by lft (that is a unique value), and
then by other (but never happen because there aren’t lft equals)… :frowning:

Andrew S. wrote:

Yep - that would kill it. Let’s try again - Can you post an example
output from:

Category.find(:all, :order => “lft”)

[[“root”, 4], ["-contry", 5], ["–andorra", 7], ["–norway", 11],
["–belgium", 12], ["-food",14], ["–thai", 15], ["–english", 18],
[“Yorkshire Pudding”, 19], [“Fish and chips”, 20]]
this is what i get now :slight_smile:
i’m thinking about that maybe it’s also possible order them when i
insert it into the db (with add_child), so i’ll have in the db something
ordered and with right lft, so i can just find(:all), without order them
on each request

marek wrote:

Andrew S. wrote:

Yep - that would kill it. Let’s try again - Can you post an example
output from:

Category.find(:all, :order => “lft”)

[[“root”, 4], ["-contry", 5], ["–andorra", 7], ["–norway", 11],
["–belgium", 12], ["-food",14], ["–thai", 15], ["–english", 18],
[“Yorkshire Pudding”, 19], [“Fish and chips”, 20]]
this is what i get now :slight_smile:
i’m thinking about that maybe it’s also possible order them when i
insert it into the db (with add_child), so i’ll have in the db something
ordered and with right lft, so i can just find(:all), without order them
on each request

ops, forget something the right one is (directly from the console):
[[“root”, 4], ["-contry", 5], ["–andorra", 7], ["–norway", 11],
["–belgium", 12], ["-food",14], ["–thai", 15], ["–english", 18],
["—Yorkshire Pudding", 19], ["—Fish and chips", 20]]

marek wrote:

i think because it first order all by lft (that is a unique value), and
then by other (but never happen because there aren’t lft equals)… :frowning:

Yep - that would kill it. Let’s try again - Can you post an example
output from:

Category.find(:all, :order => “lft”)

Andrew S. wrote:

How about just :

Category.find(:all, :order => “depth, name”)
.map{|category| ["-" * category.depth + category.name, category.id]}

Something that still is not clear to me - are the “-”'s stored in the
database? The console result you pasted suggests yes, but the code
above seems to be adding it.

There must be a way for the database to do this for us, but without the
schema is is difficult to build the correct SQL query.

it doesn’t work because it order all by depth and the by name, so it
lost the hierarchy…the result is:
[[“root”, 4], ["-contry", 5], ["-food",14], ["–andorra", 7],
["–belgium", 12], ["–english", 18], ["–norway", 11], ["–thai", 15],
["—Fish and chips", 20], ["—Yorkshire Pudding", 19]]
that isn’t right

the “-” aren’t stored in the db, they’re calculated in the map

Hey,

the problem everyone seems to be having here is forgetting that this
is a recursive data structure.

Assuming each category has a parent_id field, and assuming that root
categories have a parent_id which is nil, this should work:

in category.rb:

def self.options_for_select(records = nil)
records ||= find(:all, :order => ‘name ASC’)
records_by_parent = records.group_by(&:parent_id)

collector = lambda do |memo, category|
memo << [’*’ * category.depth + category.name, category.id]
Array(records_by_parent[category.id]).inject(memo, &collector)
end

records_by_parent[nil].inject([], &collector)
end

Regards,
Trevor

How about just :

Category.find(:all, :order => “depth, name”)
.map{|category| ["-" * category.depth + category.name, category.id]}

Something that still is not clear to me - are the “-”'s stored in the
database? The console result you pasted suggests yes, but the code
above seems to be adding it.

There must be a way for the database to do this for us, but without the
schema is is difficult to build the correct SQL query.

Trevor S. wrote:

def self.options_for_select(records = nil)
records ||= find(:all, :order => ‘name ASC’)
records_by_parent = records.group_by(&:parent_id)

collector = lambda do |memo, category|
memo << [’*’ * category.depth + category.name, category.id]
Array(records_by_parent[category.id]).inject(memo, &collector)
end

records_by_parent[nil].inject([], &collector)
end

Perfect :slight_smile:
but just one thing, about performance…if there’re a lot of people that
are loading the page i think it’ll be a problem, what do you think?
i think that it’s better order them when are inserted into the db (also
because it’s done just once, or eventually not so much frequently), so
it’s possible to get all the list order just by one select and no
operations, instead of order all for each request

Trevor S. wrote:

Hey,

the problem everyone seems to be having here is forgetting that this
is a recursive data structure.

Assuming each category has a parent_id field, and assuming that root
categories have a parent_id which is nil, this should work:

in category.rb:

def self.options_for_select(records = nil)
records ||= find(:all, :order => ‘name ASC’)
records_by_parent = records.group_by(&:parent_id)

collector = lambda do |memo, category|
memo << [’*’ * category.depth + category.name, category.id]
Array(records_by_parent[category.id]).inject(memo, &collector)
end

records_by_parent[nil].inject([], &collector)
end

Regards,
Trevor

Nice! While I can follow the code, there are a few concepts I have
never seen before. Every time I think I get it, I lose it again.

In time I will master this ruby-foo move - thanks for expanding my
knowledge :slight_smile:

daniel wrote:

it doesn’t work because it order all by depth and the by name, so it
lost the hierarchy…the result is:
[[“root”, 4], ["-contry", 5], ["-food",14], ["–andorra", 7],
["–belgium", 12], ["–english", 18], ["–norway", 11], ["–thai", 15],
["—Fish and chips", 20], ["—Yorkshire Pudding", 19]]
that isn’t right

the “-” aren’t stored in the db, they’re calculated in the map

as daniel said… :slight_smile:

thanks trevor, i’ll try… root parent_id is 0, but i think it’s not a
problem change it to nil :slight_smile:

Hey,

if the parent_id for your root(s) is 0 then you don’t have to alter
records in your database, just change the last line of the method I
gave you to this:

records_by_parent[0].inject([], &collector)

Trevor

Hey,

see below:

On 18-Jan-07, at 12:35 PM, marek wrote:

end

Perfect :slight_smile:
but just one thing, about performance…if there’re a lot of people
that are loading the page i think it’ll be a problem, what do you
think?
i think that it’s better order them when are inserted into the db
(also because it’s done just once, or eventually not so much
frequently), so it’s possible to get all the list order just by one
select and no operations, instead of order all for each request

I prefer not to speculate on whether it’ll be a performance ‘problem’

  • performance is something that can really only be determined by
    measuring on relevant hardware with relevant tests.

There’s often knee-jerk reactions to your question (which are often
right) such as “use fragment caching”. However, my attitude is: if
you can figure out a way to hyper-optimize it today, you can
certainly figure out how to hyper-optimize the day a real issue
emerges to prove this is the best place to spend your time trying
to speed things up.

If it works and it’s not a screaming obvious performance issue (like
10 second page load times) then just move on, you’re done and you’ve
probably got better things to do.

Oh, and if you really can’t let it go and you just have to try and
optimize this… then just “use fragment caching” :slight_smile:

Trevor