Limit association based on rich relation attributes - ruby-on-rails

first question for me here! Im trying to assign 'key companies' to my users. These are found in a many-to-many rich join table. On this table there are attributes like new, key, active and so forth. I want to assign companies to users in a long list and for that Im using SimpleForm.
Everything is working excepts that I want to filter out and limit the association relation based on the attributes on the rich relation. I have company relations for each user but not all of them are akey-relation or a new-relation for example. I only want the association being key to show up and not touch the other ones. I also want to set the attribute active to true when Im assigning these companies to the users. My code looks like this now:
user.rb
class User < ActiveRecord::Base
has_many :company_user_relationships
has_many :companies, through: :company_user_relationships
company.rb
class Company < ActiveRecord::Base
has_many :company_user_relationships
has_many :users, through: :company_user_relationships
schema.rb
create_table "company_user_relationships", force: true do |t|
t.integer "company_id"
t.integer "user_id"
t.boolean "key"
t.boolean "active"
t.datetime "last_contacted"
t.string "status_comment"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "status"
t.boolean "new"
end
users_controller.rb
def assign_key_companies
User.update(params[:users].keys, params[:users].values)
redirect_to(:back)
end
view
= form_for :user_companies,
url: assign_key_companies_users_path,
html: {:method => :put} do |f|
- users.each do |user|
= simple_fields_for "users[]", user do |u|
tr
td.col-md-4
= "#{user.first_name} #{user.last_name}"
td.col-md-8
= u.association :companies, label: false, collection: #key_company_candidates,
input_html: {data: {placeholder: " Assign key companies"}, class: 'chosen-select'}
= submit_tag "Save key companies", class: "btn btn-success pull-right"
I basically want to only show user.companies.where(key: true) and the SQLCOMMIT to always put the key-field to true when updating the record.
How can i filter out to only affect the associations I want?

I can think of two ways.
First to filter it at the association level
class User < ActiveRecord::Base
has_many :company_user_relationships, -> { where("company_user_relationships.key" => true) }
Or a where
user.companies.where("company_user_relationships.key" => true)
When you call user.companies it actually doing the join table among all three tables, so you could specify the condition like my example.

Related

How do I submit event on f.collection_select, and change the content displayed, dependent on what is selected in the dropdown?

I am creating a webshop with products. I sell digital game accounts.
I have invented a dropdown menu, with f.collection_select, for the user to choose which server the account must belong to.
If the user selects a server, lets say "North America", I want the page to show the accounts which I have that belong to the server "North America". I have set up two models, the server model, this model just contains name:string, and the model has_many :accounts.
Next model I have is account.rb. This model stores all information in regards to account, and belongs_to :server.
I use this form for selection of server, in my views/accounts/index.html.erb
I can see that it works, and shows my servers, which I have created in the database.
<%= form_with do |f| %>
<%= f.collection_select(:server_ids, Server.all, :id, :name, remote: true) %>
<% end %>
class CreateAccounts < ActiveRecord::Migration[6.0]
def change
create_table :accounts do |t|
t.string :name
t.string :password
t.string :BE
t.integer :level
t.integer :price
t.references :server, null: false, foreign_key: true
t.timestamps
end
end
end
class CreateServers < ActiveRecord::Migration[6.0]
def change
create_table :servers do |t|
t.string :name
t.timestamps
end
end
end
This is how the site looks atm with the dropdown:
So here I have selected EUW server. When I change it to NA, I want the page to display the accounts I have in the association Server.find(name:"Europe West (EUW)".accounts
Here is a video, that shows what I am going for:
https://www.youtube.com/watch?v=bZUxS5oMMOw

Conditional model validations based on a role

I have roles for users implemented with an enum in the user model:
enum role: [:staff, :clinician]
I have a University model with the User belongs_to :university and a University model with has_many :users.
The way that my app will work is that "staff" will belong to a university, but "clinicians" are private practice and therefore do not need to belong to a university and need not select one during signup.
I have my signup form set up to hide the university field if the user selects Clinician, but I want to make sure that my validations are set up to require that any user who selects staff on signup must also select a university and that any user who selects clinician on signup fails validation if they select a university.
Here's the role section of the user signup form:
<%= f.label :role %>
<%= f.select(:role, User.roles.keys.map {|role| [role.titleize,role]}, :include_blank => "Please Select", id: 'user_role') %>
<%= content_tag :div, class: 'js-dependent-fields', data: { 'select-id': 'user_role', 'option-value': 'staff'} do %>
<%= f.label :university_id%>
<%= collection_select( :user, :university_id, University.all, :id, :name, prompt: true) %>
It requires a bit more extra setup but I think pays off in flexibility over time:
Try Single Table Inheritance combined with your enum roles. You'll be able to more easily define separate callbacks, validations, scopes, and associations for your different roles, while inheriting the ones you want them to share in common. For example, you could just make it so only Staff belongs_to :university, and Clinician does not.
# Stripped down schema
create_table "universities", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "university_id"
t.integer "role"
t.index ["university_id"], name: "index_users_on_university_id"
end
# Models
class University < ApplicationRecord
has_many :staffs
end
class User < ApplicationRecord
self.inheritance_column = :role
enum role: { Staff: 0, Clinician: 1 }
end
class Clinician < User
end
class Staff < User
belongs_to :university
end
Staff.first.university # => returns instance of University
Clinician.first.university # => raises NoMethodError
University.first.staffs # => returns collection of Staff objects
University.first.clinicians # => raises NoMethodError
Note that there is no type column. It's been overridden by the role:integer column used for the enum by setting self.inheritance_column = :role. You can interact with the enum roles with the string/symbol representation ("Staff", Staff.new, User.first.Staff?, User.first.Staff!, User.new(role: "Staff") and ActiveRecord takes care of converting that string to the right integer for the database queries.
For example, here's the query for User.where(role: "Staff")
SELECT "users".* FROM "users" WHERE "users"."role" = 0
Staff.all returns the same result but the wording of the query is slightly different
SELECT "users".* FROM "users" WHERE "users"."role" IN (0)
See this question for more detail: Same Model with different columns Rails
You can give a condition to the validates call in your User.rb model:
validates :university, presence: true, if: lambda { self.role.to_s == 'staff' }
# watch out for symbol vs. string in your self.role array
And I think (never done it but I guess that would work) you can do this for the :clinician role:
validates :university, presence: false, if: lambda { self.role.to_s == 'clinician' }

Is there a simpler way than using STI or polymorphic associations?

I have been reading a lot on the difference between STI and polymorphic associations and decided to use STI:
user.rb
Class User < ActiveRecord::Base
has_many :articles
end
article.rb
Class Article < ActiveRecord::Base
belongs_to :users
end
sport.rb
Class Sport < Article
end
politic.rb
Class Politic < Article
end
food.rb
Class Food < Article
end
create_table "articles", force: :cascade do |t|
t.string "title"
t.string "artwork"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "approved", default: false
t.string "type"
However, upon further reading, this becomes even more complicated. All I am really looking to do is to find some way to sort my articles by type. For example, is it possible that I simply have a string column tag and specify that tag must be either politics, sports, or food?
In this case, use an enum:
#app/models/article.rb
class Article < ActiveRecord::Base
enum article_type: [:sport, :politic, :food] #-> "article_type" (int) column required
end
The only drawback to this would be that you can only assign one enum value to your model; from the use case you've outlined, it seems that's what you need.
The enum will allow you to use the following:
#article = Article.find params[:id]
#article.sport? #-> true
#article.politic? #-> false
#article.food? #-> false
#article.profile_type #-> "sport"
You also get a set of class methods to identify the various objects you need from the db:
#sports_articles = Article.sport #-> collection of "sport" articles
To create an #article through a form, you'll need collection_select:
#app/views/articles/new.html.erb
<%= form_for #article do |f| %>
<%= f.collection_select :profile_type, Article.profile_types, :first, :first %>
<%= f.submit %>
<% end %>
Update
Pagination occurs on data received from the db.
Thus, if you wanted to "include" data in the pagination, you'd just have to make sure you're pulling it from the db. To do this, you'd need to include as many article_types as you want:
#app/models/article.rb
class Article < ActiveRecord::Base
scope :by_type, (types) -> { where(article_type: Array.new(types)) }
end
This will allow you to use the following:
#articles = Article.by_type(:sport, :politic).paginate(page: params [:page], per_page: 12)
As per the docs:
Conversation.where(status: [:active, :archived])

how to join an associated table with a has_one association

In my Rails app, I only require users to enter email and name upon signup, but then give them the option to provide fuller contact details for their profile. Therefore, I have a User.rb model that has an association with Contact.rb, namely,
User.rb
has_one :contact
Contact.rb
belongs_to :user
Contact.rb has the predictable fields you might expect such as address, postal code etc, but it also stores the province_id for a relation with the Province.rb model, so
Contact.rb
attr_accessible :address, :city, :mobile, :postalcode, :province_id, :user_id
belongs_to :user
belongs_to :province
Province.rb
has_many :contacts
I did it that way (rather than storing the name of the province as a "string" on contact.rb) so that I could more easily (so I thought) categorize users by province.
In the show action of one of the artists_controller, I do the following to check whether the user is trying to sort by province and then call an artists_by_province method that does a search
if params[:province_id]
province = params[:province_id]
province = province.to_i #convert string to integer
#artistsbyprovince = User.artists_by_province(province)
else
#artists = User.where(:sculptor => true)
end
This is the method on the User.rb model that it calls if a province id is passed in
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contact: {province_id: province},
users: {sculptor: true})
}
However it gives me this error:
Could not find table 'contact'
If I make contacts plural
scope :artists_by_province, lambda {|province|
joins(:contacts).
where( contacts: {province_id: province},
users: {sculptor: true})
}
This error
Association named 'contacts' was not found; perhaps you misspelled it?
Can anyone tell me what I'm doing wrong when I'm making this query?
Update: I changed some of the details after posting because my copy and paste had some problems with it
P.S. ignore the fact that I'm searching for a 'sculptor.' I changed the names of the user types for the question.
from schema.rb
create_table "contacts", :force => true do |t|
t.string "firm"
t.string "address"
t.string "city"
t.string "postalcode"
t.string "mobile"
t.string "office"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "province_id"
end
The problem was fixed by using contact (singular) in the join and contacts (plural) in the where clause. I'm guessing 'contact' (singular) reflects the has_one association between User.rb and Contact.rb, whereas 'contacts' is used in the where clause to represent the name of the table, which is always plural.
User.rb
has_one :contact
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contacts: {province_id: province},
users: {sculptor: true})
}
Can you try the following?
scope :artists_by_province, lambda {|province|
joins(contact: {province: { id: province}}).
where(sculptor: true)
}

eager loading with attributes in join model rails

My app uses a :has_many :through association, and I'm having trouble figuring out how to most efficiently load and display data from both ends of the association and the association itself.
Here are my classes:
class Person < ActiveRecord::Base
has_many :people_ranks
has_many :ranks, :through => :people_ranks
has_many :institutions_people
has_many :institutions, :through => :institutions_people
belongs_to :school
belongs_to :department
end
class Institution < ActiveRecord::Base
has_many :institutions_people
has_many :people, :through => :institutions_people
end
class InstitutionsPerson < ActiveRecord::Base
belongs_to :institution
belongs_to :person
end
and their corresponding models:
create_table :people, :force => true do |t|
t.string :name
t.string :degree
t.integer :year_grad
t.integer :year_hired
end
create_table :institutions, :force => true do |t|
t.string :name
t.integer :ischool
end
create_table :institutions_people, :id => false do |t|
t.integer :institution_id
t.integer :person_id
t.string :rel_type
end
I want to show a person's institution info with something like #person.year_hired, #person.institution.name, and #person.institution.institutions_people.rel_type (where rel_type is either "graduated" or "hired:), but I know that third part won't work. Using the following in the show bit in the person_controller:
#person = Person.find(params[:id], :include => [:school, :department, :institutions_people, :people_ranks, {:institutions_people => :institution}, {:people_ranks => :rank}])
gives me access to #person.institutions and #person.institutions_people, but how do I connect the rel_type attribute from the join to the person-institution relationship? (I'm coming from PHP and now how to build the SQL and loop through it there, but RoR has me stumped.)
I've looked for help under "eager loading" and "associations with :has_many :through", but I get answers about building the associations. My question is really about accessing the association's data after it exists. My app uses static data, and I'm not worried about the update, destroy, or create methods. Thank you for your help!
The way to access the data is through the institutions_people association. So, you would do something like:
me = Person.first
rel = me.institutions_people.first
And then in the view
<%= rel.rel_type %> from <%= rel.institution.name %>
Alternatively, you can give yourself a full list of institutions along with their info:
me = Person.first
And then in the view:
<% for ip in me.institutions_people %>
<%= ip.rel_type %> from <%= ip.institution.name %>
<% end %>

Resources