RoR scope on each array-object - ruby-on-rails

i am pretty new to RoR so this might be a newbie question but i couldn't find an answer to it and solve the problem.
I need to check every single element from a dynamic array (which is created by a HABTM connection over a join table) for a condition.
Right now i have something like this:
scope :filter, lambda { |devicefilter, testtypefilter|
{
testtypefilter.each { |testtype|
:include => :tests,
:conditions => ['(tests.device != ? OR tests.device IS ?) AND ? NOT IN tests.testtypes', devicefilter, nil, testtype]
}
}
}
This shows me a syntax error.
But i think even if i manage to get it working this way it would not perform the sql query correctly since ts and tts is connected by HABTM, so the actualy value in the table is NULL and i would have to create another join in that query.
I have really no clue how to solve this.
Any hints would be helpful.
Thanks,
Niko
Edit:
My Controller looks like this
if params[:d] == nil or params[:t] == nil
#users = User.all
else
#users = User.filter(params[:d], params[:t])
end
:d is a fixed value which is comming from a select_tag
:t is an array which is comming from a bunch of dynamic checkboxes
the values are all fine and being passed correctly to the Model. So the view or controller shouldn't be the problem.
Edit2:
Since it looks a bit unclear, here is the current situation:
There is a form which consists of a Select-Box and a bunch of Checkboxes.
The Select-Box is getting its values from the table "devices" and after the form is submitted it passes the selected value to the controller as the param :d.
The Checkboxes are a list from all entries of the table "testtypes" and after the form is submitted it passes an array of all checked testtype_ids from the table to the controller as the param :t. (works correctly)
Now there is another table "tests" which has a HABTM connection to all entries in devices and testtypes, so i can gather special connections from 1 device and and multiple testtypes together in a single entry.
Now in the table "users" there is a column "tests" which refers to the table "tests" in a HABTM connection so 1 user can have (participated in) multiple tests (which can have multiple testtypes).
So after the form is submitted the values :d and :t should be used for a sql query to filter all users by tests (while tests are dependant by devices and testtypes). So all users should by checked in the column tests by the values of the referring tests in devices (:d) and testtypes (:t[]).
But since testtypes (:t[]) is being passed as an array i have to check somehow every single element of that array with a sql request. Or is there a way to check for the whole array?
Example:
There are 3 Devices: (dynamic table)
A01
A02
A03
There are 3 Testtypes: (dynamic table)
TT01
TT02
TT03
There are 4 Tests: (dynamic table with HABTM devices and testtypes)
T1 = A01 - TT01/TT03
T2 = A01 - TT03
T3 = A02 - TT02/TT03
T4 = A03 - TT01/TT02/TT03
There are 5 Users: (dynamic table with HABTM tests)
U1 = ... T1 ...
U2 = ... T2/T4 ...
U3 = ... T3/T4 ...
U4 = ... T1/T2/T3 ...
U5 = ... T3/T4 ...
Now the Form will look like:
Device: Select-Box={A01/A02/A03}
Testtypes: Checkboxes={TT01/TT02/TT03}
If i select now A01, check TT01+TT02 and submit the query should return every single User who has not participated in the following Tests:
A01 - TT01
A01 - TT02
So at last i get a list of users that i could use for the test A01 - TT01+TT03, since there are no conflicts.
So the query would return every user who has not participated in T1, since that is the only conflict.
So the userlist would look like:
U2
U3
U5
Can someone help me with this?
Noone got an idea? :'(

Sounds like you want something like
scope :filter, lambda {|df| includes(:ts).where("foo_id in (?)", df)
which will allow you to do
SomeModel.filter([23,24])
And that that array would be passed into the lambda for you to use in the conditions.

Related

does x = User.all create a hash? How do I traverse it?

Let's say I have a User table and a Messages table, they have a has_many belongs_to relationship. I want to find the id: for users who's names are "Bob", then pull the message history for one of the id's.
x = User.where(name: "Bob")
Does that create a hash in variable x, with all the results of users whose names were Bob? The result in the console certainly looks like a hash when I run x. To includes the messages tied to all the Bobs, I think I do:
x = User.where(name: "Bob").includes(:messages)
Now that I have x...how do I find the id's of the people whose names are Bob? I don't want to query the db again, I'd like to do it all via the variable, is that possible?
I then want to get the first message of the first id (the first Bob) in my table. Can that be done via the variable, or do I have to go back to the DB once I have the first id?
Thanks for all the help guys and gals!
Most ActiveRecord queries return a Relation.
You can call x = x.to_a to make rails perform the actual query(there will be 2 SQL queries - one for users and one for messages) and then traverse the resulting array.
This will do it. As referenced in the rails guides. http://guides.rubyonrails.org/active_record_querying.html section 13.2
x = Message.includes(:users).where(users: { name: "Bob"})
and then to get the first message just tack on .first at the end of the query.
x = Message.includes(:users).where(users: { name: "Bob"}).first
You need to query from Message, not User. Joins (inner join) and includes (left outer join) can be used for eager loading, like in your question, or to do query across multiple tables.
Message.joins(:user).where('user.name = "bob"')

includes/joins case in rails 4

I have a habtm relationship between my Product and Category model.
I'm trying to write a query that searches for products with minimum of 2 categories.
I got it working with the following code:
p = Product.joins(:categories).group("product_id").having("count(product_id) > 1")
p.length # 178
When iterating on it though, for each time I call product.categories, it will do a new call to the database - not good. I want to prevent these calls and have the same result. Doing more research I've seen that I could include (includes) my categories table and it would load all the table in memory so it's not necessary to call the database again when iterating. So I got it working with the following code:
p2 = Product.includes(:categories).joins(:categories).group("product_id").having("count(product_id) > 1")
p2.length # 178 - I compared and the objects are the same as last query
Here come's what I am confused about:
p.first.eql? p2.first # true
p.first.categories.eql? p2.first.categories # false
p.first.categories.length # 2
p2.first.categories.length # 1
Why with the includes query I get the right objects but I don't get the categories relationship right?
It has something to do with the group method. Your p2 only contains the first category for each product.
You could break this up into two queries:
product_ids = Product.joins(:categories).group("product_id").having("count(product_id) > 1").pluck(:product_id)
result = Product.includes(:categories).find(product_ids)
Yeah, you hit the database twice, but at least you don't go to the database when you're iterating.
You must know that includes doesn't play well with joins (joins will just suppress the former).
Also When you include an association ActiveRecord figures out if it'll use eager_load (with a left join) or preload (with a separate query). Includes is just a wrapper for one of those 2.
The thing is preload plays well with joins ! So you can do this :
products = Product.preload(:categories). # this will trigger a separate query
joins(:categories). # this will build the relevant query
group("products.id").
having("count(product_id) > 1").
select("products.*")
Note that this will also hit the database twice, but you will not have any O(n) query.

Rails: how to correctly modify and save values of records in join table

I would like to understand why in Rails 4 (4.2.0) I see the following behaviour when manipulating data in a join table:
student.student_courses
returns all associated records of courses for a given user;
but the following will save changes
student.student_courses[0].status = "attending"
student.student_courses[0].save
while this will not
student.student_courses.find(1).status = "attending"
student.student_courses.find(1).save
Why is that, why are those two working differently, is the first one the correct way to do it ?
student.student_courses[0] and student.student_courses.find(1) are subtly different things.
When you say student.student_courses, you're just building a query in an ActiveRecord::Relation. Once you do something to that query that requires a trip to the database, the data is retrieved. In your case, that something is calling [] or find. When you call []:
student.student_courses[0]
your student will execute the underlying query and stash all the student_courses somewhere. You can see this by looking at:
> student.student_courses[0].object_id
# and again...
> student.student_courses[0].object_id
# same number is printed twice
But if you call find, only one object is retrieved and a new one is retrieved each time:
> student.student_courses.find(1).object_id
# and again...
> student.student_courses.find(1).object_id
# two different numbers are seen
That means that this:
student.student_courses[0].status = "attending"
student.student_courses[0].save
is the same as saying:
c = student.student_courses[0]
c.status = "attending"
c.save
whereas this:
student.student_courses.find(1).status = "attending"
student.student_courses.find(1).save
is like this:
c1 = student.student_courses.find(1)
c1.status = "attending"
c2 = student.student_courses.find(1)
c2.save
When you use the find version, you're calling status= and save on entirely different objects and since nothing was actually changed in the one that you save, the save doesn't do anything useful.
student_courses is an ActiveRecord::Relation, basically a key => value store. The find method would only work on a model

querying active record

i am trying to query my postgres db from rails with the following query
def is_manager(team)
User.where("manager <> 0 AND team_id == :team_id", {:team_id => team.id})
end
this basically is checking that the manager is flagged and the that team.id is the current id passed into the function.
i have the following code in my view
%td= is_manager(team)
error or what we are getting return is
#<ActiveRecord::Relation:0xa3ae51c>
any help on where i have gone wrong would be great
Queries to ActiveRecord always return ActiveRecord::Relations. Doing so essentially allows the lazy loading of queries. To understand why this is cool, consider this:
User.where(manager: 0).where(team_id: team_id).first
In this case, we get all users who aren't managers, and then we get all the non-manager users who are on team with id team_id, and then we select the first one. Executing this code will give you a query like:
SELECT * FROM users WHERE manager = 0 AND team_id = X LIMIT 1
As you can see, even though there were multiple queries made in our code, ActiveRecord was able to squish all of that down into one query. This is done through the Relation. As soon as we need to actual object (i.e. when we call first), then ActiveRecord will go to the DB to get the records. This prevents unnecessary queries. ActiveRecord is able to do this because they return Relations, instead of the queried objects. The best way to think of the Relation class is that it is an instance of ActiveRecord with all the methods of an array. You can call queries on a relation, but you can also iterate over it.
Sorry if that isn't clear.
Oh, and to solve your problem. %td = is_manager(team).to_a This will convert the Relation object into an array of Users.
Just retrieve first record with .first, this might help.
User.where("manager <> 0 AND team_id == :team_id", {:team_id => team.id}).first

ActiveRecord find and only return selected columns

edit 2
If you stumble across this, check both answers as I'd now use pluck for this
I have a fairly large custom dataset that I'd like to return to be echoe'd out as json. One part is:
l=Location.find(row.id)
tmp[row.id]=l
but I'd like to do something like:
l=Location.find(row.id).select("name, website, city")
tmp[row.id]=l
but this doesn't seem to be working. How would I get this to work?
thx
edit 1
alternatively, is there a way that I can pass an array of only the attributes I want included?
pluck(column_name)
This method is designed to perform select by a single column as direct SQL query Returns Array with values of the specified column name The values has same data type as column.
Examples:
Person.pluck(:id) # SELECT people.id FROM people
Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
Person.where(:confirmed => true).limit(5).pluck(:id)
see http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck
Its introduced rails 3.2 onwards and accepts only single column. In rails 4, it accepts multiple columns
In Rails 2
l = Location.find(:id => id, :select => "name, website, city", :limit => 1)
...or...
l = Location.find_by_sql(:conditions => ["SELECT name, website, city FROM locations WHERE id = ? LIMIT 1", id])
This reference doc gives you the entire list of options you can use with .find, including how to limit by number, id, or any other arbitrary column/constraint.
In Rails 3 w/ActiveRecord Query Interface
l = Location.where(["id = ?", id]).select("name, website, city").first
Ref: Active Record Query Interface
You can also swap the order of these chained calls, doing .select(...).where(...).first - all these calls do is construct the SQL query and then send it off.
My answer comes quite late because I'm a pretty new developer. This is what you can do:
Location.select(:name, :website, :city).find(row.id)
Btw, this is Rails 4

Resources