How can I query a polymorphic association by id and attribute? - ruby-on-rails

I am currently build a Rails 5.2 app.
In this app I have three objects, Project, Customfield and CustomfieldAnswer.
Project:
- id
- title
Customfield
- id
- title
CustomfieldAnswer
- id
- answerable_id
- answerable_type
- customfield_id
- answer
A Project has_many CustomfieldAnswers.
A CustomfieldAnswer is connected with a polymorphic association (answerable).
I tried this query (has_answer) and it returns all CustomfieldAnswers that matches but how can I use that method (or another) in a query for Projects that got these:
class CustomfieldAnswer < ApplicationRecord
include Defaultable
belongs_to :customfield
belongs_to :answerable, polymorphic: true
def self.has_answer(customfield_id, answer)
CustomfieldAnswer.where(customfield_id: customfield_id, answerable_type: 'Project', answer: answer)
end
end
I want to query all Projects that has a specific CustomfieldAnswer (by it's Id) with a specific answer (by string). How can I do that?
Thank you!

Related

How to access has_many through relationship possible in rails?

How can I access my related records?
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, through: :post_categories
class Categories < ActiveRecord::Base
has_many :post_categories
has_many :post, through: :post_categories
class PostCategories < ActiveRecord::Base
belongs_to :post
belongs_to :category
PostCategories table has id, posts_id, and categories_id columns.
id | posts_id | categories_id
1. | 2 | 3
2. | 2 | 4
What I want is: to get posts related to a category. like: all Posts where in x category.
Yep, this is an easy one.
one_or_more_categories = # Category.find... or Category.where...
posts = Post.joins(:categories).where(category: one_or_more_categories)
Rails is clever enough to take either a model or a query that would find some data and turn that into an efficient appropriate query, that might be a subquery. Trying things out in the Rails console (bundle exec rails c) is a good way to see the generated SQL and better understand what's going on.
(EDIT: As another answer points out, if you've already retrieved a specific Category instance then you can just reference category.posts and work with that relationship directly, including chaining in .order, .limit and so-on).
Another way to write it 'lower level' would be:
Post.joins(:categories).where(category: {id: one_or_more_category_ids})
...which is in essence what Rails will be doing under the hood when given an ActiveRecord model instance or an ActiveRecord::Relation. If you already knew the e.g. category "name", or some other indexed text column that you could search on, then you'd adjust the above accordingly:
Post.joins(:categories).where(category: {name: name_of_category})
The pattern of joins and where taking a Hash where the join table name is used as a key with values nested under there can be taken as deep as you like (e.g. if categories had-many subcategories) and you can find more about that in Rails Guides or appropriate web searches. The only gotcha is the tortuous singular/plural stuff, which Rails uses to try and make things more "English-y" but sometimes - as in this case - just creates an additional cognitive burden of needing to remember which parts should be singular and which plural.
Not sure if this answers it but in ActiveRecord your Post will have direct access to your Category model and vice versa. So you could identify the category you want the posts from in a variable or an instance variable, and query #specific_category.posts. If you are doing this in your controller, you could even do it in before_action filter. If you are using it in serializers its not much different.
You could also create a scope in your Post model and use either active record or raw SQL to query specific parameters.
You also have an error in your Category model. Has many is always plural so it would be has_many :posts, through: :post_categories
Get the category object and you can directly fetch the related posts. Please see the following
category = Category.find(id)
posts = category.posts
Since you have already configured the has_many_through relation, rails will fetch post records related the category.

In Ruby on Rails, how can I create a scope for a has_many relationship?

Here is an example:
Let says I have a Student object which has a has_many relationship with ReportCard objects. ReportCard objects have a boolean field called "graded" that flags they have been graded. So it looks like:
class Student < ActiveRecord
has_many :report_cards
end
class ReportCard < ActiveRecord
# graded :boolean (comes from DB)
belongs_to :student
end
Now, let's say you want to create a default scope so that if a student has no graded ReportCards, you want to see all of them, but if they have at least one graded ReportCard, you only want to see the graded ones. Finally, let's say you order them by "semester_number".
Using this scope on ReportCard works correctly:
scope :only_graded_if_possible, ->(student) { where(graded: true, student: student).order(:semester_number).presence || order(:semester_number) }
But I want it to be the default scope for Student so I tried:
class Student < ActiveRecord
has_many :report_cards, ->{ where(graded: true).order(:semester_number).presence || order(:semester_number) }
end
but this does not work. It won't return any report_cards if there is a single graded report_card in the whole db. Looking at the queries that are run, first it runs something like:
SELECT report_cards.* FROM report_cards WHERE reports_cards.graded = t ORDER BY semester_number ASC
I think this must be the present? check part of the presence query and notice it does not filter on Student at all! So if there is a single report_card that is graded, the check passes and then it runs the following query to get what to return:
SELECT report_cards.* FROM reports_card WHERE report_card.student_id = 'student id here' AND report_card.graded = t ORDER BY semester_number
This query actually would be correct if the student had a graded report card but it is always empty if he does not.
I assume that possibly the filtering on Student is added afterwards. So I tried to somehow to get it to filter student right off the bat:
has_many :report_cards, ->{ where(graded: true, student: self).order(:semester_number).presence || order(:semester_number) }
This does not work either because it appears that "self" in this scope is not the Student object like I'd assume, but a list of all the report_card ids. Here is the query this one runs:
SELECT report_cards.* FROM report_cards WHERE report_cards.graded = t AND report_cards.student_id IN (SELECT report_cards.id FROM report_cards) ORDER BY semester_number ASC
That isn't even close to correct. How can I get this to work?
I think what it really comes down to is someone being able to pass "self" (meaning the current Student object) as a parameter into the scope being applied in the "has_many". Maybe that isn't possible.
You can pass object to has_many scope as a parameter to lambda
has_many :report_cards, -> (student) { ... }
Try this:
class Student < ActiveRecord::Base
has_many :report_cards, ->{ where(graded: true).order(:semester_number).presence || unscoped.order(:semester_number) }
end
I am using this in my project where I have a model which is associated with users:
has_many :users, -> { only_deleted }
And in the Users model create a scope of only_deleted, returning your users which are deleted.

Using Retrieval Multiple Objects for another Retrieval in Active Records/Ruby on Rails

Kind of new to Ruby/Rails, coming from c/c++, so I'm doing my baby steps.
I'm trying to find the most elegant solution to the following problem.
Table A, among others has a foreign key to table B (let's call it b_id), and table B contains a name field and a primary (id).
I wish to get a list of object from A, based on some criteria, use this list's b_id to access Table B, and retrieve the names (name field).
I've been trying many things which fail. I guess I'm missing something fundamental here.
I tried:
curr_users = A.Where(condition)
curr_names = B.where(id: curr_users.b_id) # fails
Also tried:
curr_names = B.where(id: curr_users.all().b_id) # fails, doesn't recognize b_id
The following works, but it only handles a single user...
curr_names = B.where(id: curr_users.first().b_id) # ok
I can iterate the curr_users and build an array of foreign keys and use them to access B, but it seems there must be more elegant way to do this.
What do I miss here?
Cheers.
Assuming you have following models:
class Employee
belongs_to :department
end
class Department
has_many :employees
end
Now you can departments based on some employee filter
# departments with employees from California
Department.include(:employees).where(:employees => {:state => "CA"}).pluck(:name)
For simplicity, let's take an example of Article and Comments, instead of A and B.
A Comment has a foreign key article_id pointing at Article, so we can setup a has_many relationship from Article to Comment and a belongs_to relationship from Comment to Article like so:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Once you have that, you will be able do <article>.comments and Rails will spit out an array of all comments that have that article's foreign key. No need to use conditionals unless you are trying to set up a more complicated query (like all comments that were created before a certain date, for example).
To get all the comment titles (names in your example), you can do <article>.comments.map(&:title).

Help with Associations in Rails 3

Ruby: 1.9.2
Rails: 3.0beta3
I need some help with associations in Rails 3.
I have the following models (see excerpts below):
School, State, SchoolLocale
The schools table has the following fields:
id, name, state_id, school_locale_id
The states table has the following fields:
id, abbr, name
The school_locales table has the following fields:
id, code, name
Unfortunately, my data-source didn't have IDs for school_locales. Thus, the data stored in the 'school_locale_id' field in the schools table actually maps to the 'code' field in the school_locales table.
school.rb:
class School < ActiveRecord::Base
belongs_to :state
belongs_to :school_locale
end
state.rb:
class State < ActiveRecord::Base
has_many :schools
end
school_locale.rb:
class SchoolLocale < ActiveRecord::Base
has_many :schools
end
I would like a query for a given school, let's say School.find(1), that would output the school name, the state name and the school-locale name. I assume that I need to add an index to the 'code' field in the school_locales table and somehow specify it as a foreign key, but I'm not certain. Any help would be appreciated.
This doesn't exactly answer your question, but I think it is a useful bit of information. Regarding your use of a states table, let me refer to Surrogate Vs. Natural/Business Keys.
#Ted says here:
Remember there is nothing special about a primary key, except that it is labelled as such. It is nothing more than a NOT NULL UNIQUE constraint, and a table can have more than one.
If you use a surrogate key, you still want a business key to ensure uniqueness according to the business rules.
There's no point in having a state_id foreign key that links to a states table. Each state already has a unique id; its 2-letter abbreviation. This unique id is just as good as a numeric one. And because this data doesn't change often, there's no harm in having it statically defined within your application somewhere.
I'm not really sure about it, but you could try using this:
class SchoolLocale < ActiveRecord::Base
has_many :schools, :primary_key => :code
end
Let me know if it works :]

Help me understand joins in ruby on rails

Warning, I am new to ruby on rails. I know my database isn't setup all that great, but we're pulling in from a remote database and storing information from that database.
Users:
- id
- ...
stations
- id
- user_id
- hex_key (unique)
- ...
calls
- id
- reported by (hex key from stations)
- data source id (from remote database)
call details
- id
- call_index (data source id from calls)
responses
- id
- call_index (data source id from calls)
- response_id (from remote database)
response details
- id
- response_index (response_id from responses)
As far as the models go (this is all I have completed so far) I think this is also my biggest problem:
user has many stations, calls through stations and reports through calls
stations has many calls
calls has many responses and belongs to stations
response belongs to a call
I've been trying to figure this out, but how do i model this so i can get everything from the users correctly. something like this:
#user.responses.find(:all)
and that would give all the responses for that user
here is some information on joining tables.
http://www.ruby-forum.com/topic/64839
Based on the database schema and description here your models would look something like:
class User < ActiveRecord::Base
has_many :stations
end
class Station < ActiveRecord::Base
belongs_to :user
has_many :calls, :foreign_key => "reported by"
end
class Call < ActiveRecord::Base
has_many :call_details, :foreign_key => "call_index"
end
The difficulty here is that the database schema is not exactly what Rails expects by default (the convention). You will also probably need to specify the primary key for the tables if they are not using the ID.
Also, I am not sure you will be able to construct #user.responses because responses belong to calls, and calls belong to stations, and stations belong to users. This is effectively #user.stations.calls.responses.

Resources