Habtm on an array

Hi,

I have three Models

User, Group and File.

User habtm group
Group habtm File

How can I find all that files that a particular user has access to?
eg user.groups.files ?

On May 31, 2006, at 12:28, Daniel wrote:

eg user.groups.files ?
The Rails-specific list (http://lists.rubyonrails.org/mailman/
listinfo/rails) and wiki and things may give you a better answer
(particularly in terms of things like query optimisation) than you’ll
get here, but…

user.groups is an array, it’s not the array which has a file
attribute, but each individual group in it, so maybe something like
this:

class User
def files
self.groups.inject([]) { |arr, group| arr + group.files }
end
end

matt smillie.

On Wednesday 31 May 2006 5:28 am, Daniel wrote:

I have three Models

User, Group and File.

User habtm group
Group habtm File

How can I find all that files that a particular user has access to?
eg user.groups.files ?

Step back from the problem a step. Forget that you are working with
models or
that you are dealing with habtm relationships in Rails.

user.groups returns an array. And each of the elements in that array,
if you
call files() on it, returns another array.

So all you are really looking at here is a requirement to iterate over
an
array of arrays, creating a set consisting of each of the unique values
from
those arrays. Strip all the Rails stuff away, and it’s just a simple
data
problem.

So, create a simple surrogate to think about:

a = [[1,2,3],
[2,3,4],
[3,4,5],
[4,5,6],
[5,6,7],
[6,7,8]]

That’s just an array of arrays. Arrays in Ruby provide some handy
methods for
treating them like sets. And what you want is the union of all of your
sets.
With an array, | performs a union operation. So:

[1.,2,3] | [2,3,4]

returns

[1,2,3,4]

So all that you need to do now is iterate through your array applying
each sub
array to a common array with the union operator.

arr = []
a.each {|elem| arr = arr | elem}

That works. You get:

[1,2,3,4,5,6,7,8]

FYI, Ruby also provides a method, inject(), that is perfect for this
kind of
operation. So, the above code can be rewritten like so:

a.inject([]) {|arr,elem| arr | elem}

inject() just takes a starting value and a block. The arguments to the
block
are an accumulator variable, which is initialized to the starting value
passed to inject, and each successive element of the array inject it
iterating over. The code in the block is evaluated and the result of
the
code is inserted into the accumulator variable. When all values of the
array
have been iterated over, inject() returns the final value of the
accumulator.
It’s the loop for code that needs to accumulate values.

Anyway, that’s the way to solve the problem with an arbitrary set of
arrays.
So, now just apply it to your model.

Something like this, probably:

class User
def files
groups.inject([]) {|all_files, some_files| all_files | some_files}
end
end

Hope this helps,

Kirk H.

-----Original Message-----
From: Kirk H. [mailto:[email protected]]

[…nice breakdown…]

a.inject([]) {|arr,elem| arr | elem}

a.flatten.uniq ?

[…]
Something like this, probably:

class User
def files
groups.inject([]) {|all_files, some_files| all_files |
some_files}
end
end

Should be equivalent to:

groups.flatten.uniq

But does probably not work because (as you explained)

“…And each of the elements in that array, if you
call files() on it, returns another array”

class User
def files
groups.inject([]) {|all_files, group| all_files | group.files}
end
end

Hope this helps,

Kirk H.

cheers

Simon

class User
def files
groups.inject([]) {|all_files, group| all_files | group.files}
end
end

Doh! Yeah. I goofed. Thanks.

Kirk H.

We all do that - from time to time :slight_smile:

As Rails implements Symbol#to_proc one should be able to do

myuser.groups.map(&:files).flatten.uniq

but that is getting esotheric.

cheers

Simon

On Wednesday 31 May 2006 7:23 am, Kroeger, Simon (ext) wrote:

Should be equivalent to:
groups.inject([]) {|all_files, group| all_files | group.files}
end
end

Doh! Yeah. I goofed. Thanks.

Kirk H.