Rails 4, SQ Lite 3: Access model through model without an association - ruby-on-rails

I've got two tables:
item
recipe
In this example I've got an item object item with the id 1.
The item table has one column named recipe_id. For this object it has the value 2.
recipe has a column named title.
How can I get for example the title from the recipe of which the id is linked in item, if I have the item?
Something like...?
= item.recipe_id.title
Thanks in advance for any help!

you'd use your own SQL to find records in a tableusing find_by_sql. The find_by_sql method will return an array of objects even if the underlying query returns just a single record. For example you could run this query:
for example
Recipe.joins("INNER JOIN item ON <your_conditions_to_get_recipe_related_to_item>")
or
Recipe.find_by_sql("SELECT * FROM recipe
INNER JOIN item ON <your_conditions_to_get_recipe_related_to_item>")
acording to your case
for example
Recipe.joins("INNER JOIN item ON item.recipe_id = recipe.id and item.id = 1 and recipe.id = 2")
with params
Recipe.joins("INNER JOIN item ON item.recipe_id = recipe.id").where("item.id = ? and recipe.id = ?", item_id, recipe_id)
these are some ways to access to the database and retrieve a model or models instances without associations ways.

Related

Rails Postgres query to exclude any results that contain one of three records on join

This is a hard problem to describe but I have Rails query where I join another table and I want to exclude any results where the join table contain one of three conditions.
I have a Device model that relates to a CarUserRole model/record. In that CarUserRole record it will contain one of three :role - "owner", "monitor", "driver". I want to return any results where there is no related CarUserRole record where role: "owner". How would I do that?
This was my first attempt -
Device.joins(:car_user_roles).where('car_user_roles.role = ? OR car_user_roles.role = ? AND car_user_roles.role != ?', 'monitor', 'driver', 'owner')
Here is the sql -
"SELECT \"cars\".* FROM \"cars\" INNER JOIN \"car_user_roles\" ON \"car_user_roles\".\"car_id\" = \"cars\".\"id\" WHERE (car_user_roles.role = 'monitor' OR car_user_roles.role = 'driver' AND car_user_roles.role != 'owner')"
Update
I should mention that a device sometimes has multiple CarUserRole records. A device can have an "owner" and a "driver" CarUserRole. I should also note that they can only have one owner.
Anwser
I ended up going with #Reub's solution via our chat -
where(CarUserRole.where("car_user_roles.car_id = cars.id").where(role: 'owner').exists.not)
Since the car_user_roles table can have multiple records with the same car_id, an inner join can result in the join table having multiple rows for each row in the cars table. So, for a car that has 3 records in the car_user_roles table (monitor, owner and driver), there will be 3 records in the join table (each record having a different role). Your query will filter out the row where the role is owner, but it will match the other two, resulting in that car being returned as a result of your query even though it has a record with role as 'owner'.
Lets first try to form an sql query for the result that you want. We can then convert this into a Rails query.
SELECT * FROM cars WHERE NOT EXISTS (SELECT id FROM car_user_roles WHERE role='owner' AND car_id = cars.id);
The above is sufficient if you want devices which do not have any car_user_role with role as 'owner'. But this can also give you devices which have no corresponding record in car_user_roles. If you want to ensure that the device has at least one record in car_user_roles, you can add the following to the above query.
AND EXISTS (SELECT id FROM car_user_roles WHERE role IN ('monitor', 'driver') AND car_id = cars.id);
Now, we need to convert this into a Rails query.
Device.where(
CarUserRole.where("car_user_roles.car_id = cars.id").where(role: 'owner').exists.not
).where(
CarUserRole.where("car_user_roles.car_id = cars.id").where(role: ['monitor', 'driver']).exists
).all
You could also try the following if your Rails version supports exists?:
Device.joins(:car_user_roles).exists?(role: ['monitor', 'driver']).exists?(role: 'owner').not.select('cars.*').distinct
Select the distinct cars
SELECT DISTINCT (cars.*) FROM cars
Use a LEFT JOIN to pull in the car_user_roles
LEFT JOIN car_user_roles ON cars.id = car_user_roles.car_id
Select only the cars that DO NOT contain an 'owner' car_user_role
WHERE NOT EXISTS(SELECT NULL FROM car_user_roles WHERE cars.id = car_user_roles.car_id AND car_user_roles.role = 'owner')
Select only the cars that DO contain either a 'driver' or 'monitor' car_user_role
AND (car_user_roles.role IN ('driver','monitor'))
Put it all together:
SELECT DISTINCT (cars.*) FROM cars LEFT JOIN car_user_roles ON cars.id = car_user_roles.car_id WHERE NOT EXISTS(SELECT NULL FROM car_user_roles WHERE cars.id = car_user_roles.car_id AND car_user_roles.role = 'owner') AND (car_user_roles.role IN ('driver','monitor'));
Edit:
Execute the query directly from Rails and return only the found object IDs
ActiveRecord::Base.connection.execute(sql).collect { |x| x['id'] }

How Do I Use Joins for Three Tables in Ruby on Rails?

I am working on a Ruby on Rails application which already has logic for text searching using pg_search and two other fields on a model. The logic creates an 'array' of rows from the search result. I do not remember the actual name of this since technically this is not an array. It is an instance variable of selected rows. I want to add search criteria from two additional models. My database is PostgreSQL.
Here is a subset of all three model definitions:
MediaLibrary: name, media_creator_id, media_type_id (fields that are being used in current search; has many media_topics and has many media_targets)
MediaTopic: media_library_id, topic_id (want to search for topic_id; belongs to media_library; topic_id being searched is coming from a Topic model (id, name))
MediaTarget: media_library_id, target_id (want to search for target_id; belongs to media_library; target_id being searched is coming from a Target model (id, name))
I'm thinking that I should be able to do something like this if both topic and target are being searched along with the other three search criteria. I will also need to have topic_id and target_id in my results so I can display Topic.name and Target.name on my view.
#media_libraries = MediaLibrary.text_search(params[:query]).where("media_creator_id = ? AND media_type_id = ?", params[:media_creator_id].to_i, params[:media_type_id].to_i).joins(:media_topics.where("media_library_id = ? and topic_id = ?", id_from_media_library_row, params[:topic_id].to_i).joins(:media_targets.where("media_library_id = ? and target_id = ?", id_from_media_library_row, params[:target_id].to_i)
I have searched on postgresql.org and Stack Overflow but have not found anything joining three tables using Ruby on Rails that was answered by anyone.
You can pass a SQL join statement into #joins. I'm not sure what it'd be in your case but you can do something like:
#media_libraries = MediaLibrary.joins(%q(
JOIN media_targets
ON media_targets.media_library_id = media_libraries.id
JOIN media_topics
ON media_topics.media_library_id = media_libraries.id
)).text_search(params[:query])
.where(
media_libraries: {
media_creator_id: params[:media_creator_id],
media_type_id: params[:media_type_id]
},
media_topics: { id: params[:topic_id] },
)

Rails - How Do I do this Query? Get One of Each Record

I have a BlogPost model with a :category attribute. I am making a new BlogPost form, and I would like for the select menu to populate itself with each category entered in previous records by the user.
Therefore, I need one query to find all BlogPosts with a User ID, and then round up a list of each category they have entered. The same category will exist in multiple records, but of course I only want to return copy of it for the select menu.
Thank you :)
You can SELECT DISTINCT categories returned an INNER JOIN to the right user :
Category
.joins( :posts )
.where( posts: { user_id: current_user.id } )
.uniq
This should send a query like this :
SELECT DISTINCT categories.*
FROM categories
INNER JOIN posts ON posts.category_id = categories.id
WHERE posts.user_id = [whatever]
EDIT NOTE: be wary that uniq is both a method on a Relation and on an Array. Be sure to call it on the relation before it is cast to an array, or you will perform the uniq on an array of non-distinct results, which works too, but is absurd performance-wise.

Trying to return AREL not Array in Rails

I have two models: Products and Tags through a products_tags join in a HABTM relationship.
I am currently defining my controller index as:
#stats = Product.all(:include => :tags).uniq
which returns an array. How can I return an Active Rel object? I tried added scoped, however received a no method error.
I need to find and list a unique list of tags, and be able to view what product each specif tag belongs to.
Try #stats = Product.includes(:tags).uniq.
Note that uniq turns the Relation into an Array; if you want to do something like SELECT DISTINCT you'll want to use Product.includes(:tags).select('DISTINCT whatever').

IF/CASE statement OR MySQL OR array for get category name

On my site I got entries which have category. Site have only 5 categories, so I have dilemma:
Make relationship between category table and entries (category_id) table
OR
Make method which return category name via IF/CASE statement? Like:
case #entry.category.id
when 1
"Games"
when 2
"Movies"
when 3
"Fun"
[...]
end
(I remind that I must get 10 category name per page)
OR
Use array:
cat[1] = "Games"
cat[2] = "Movies"
cat[3] = "Fun"
[...]
<%= cat[#entry.category.id] %>
I think this relation definitely belongs into the database. (adding a category table)
it is the most sane and most scalable option.
It is also the cleanest, because you break the seperation of data, display and logic (MVC: model, view, controller) when hardcoding the categories in your application.
you can easily select the item AND its category with a single query:
SELECT item.*, category.name
FROM item
LEFT JOIN category ON category.id = item.category_id
WHERE some=condition
there are similar queries for INSERTs and UPDATEs (at least in MySQL), so you never need a second query.
If the only thing you care about category is "name", then you should just store the category_name in the entries table.
OR
Make a constant CATEGORY_NAME and wrapper method to get the name with id in the entries table (without using Category table/model at all). eg.,
class Entry
CATEGORY_NAME = [ "Games", "Movies", "Fun"]
def category_name
CATEGORY_NAME[cat_id] #cat_id being just 0,1,2 .. depends how you want to store
end
...
I am sure there are many ways to achieve this anyway.
Hope it helps.

Resources