I have a Ruby on rails application where users can add one or more software.
These users can then subscribe (paid mode).
I would like to be able to display all paid user software only.
I tried several things and still can not find the solution.
for example:
Software.includes(:users).where(user: {subscribed: true})
EDIT:
Model USER:
class User < ActiveRecord::Base
has_many :softwares
end
Model Softwares:
class Software < ActiveRecord::Base
belongs_to :user
end
JoinTable
class CreateJoinTableUsersSoftwares < ActiveRecord::Migration[5.2]
def change
create_join_table :users, :softwares do |t|
t.index [:user_id, :software_id]
t.index [:software_id, :user_id]
end
end
end
ERROR with :
Software.includes(:users).where(user: {subscribed: true})
ActiveRecord::ConfigurationError (Can't join 'Software' to association named 'users'; perhaps you misspelled it?)
I think your error is here:
Software.includes(:users).where(user: {subscribed: true})
The includes should reflect the association, in this case a singular user. This is what's causing the error you're seeing.
Also, it's a common gotcha, but the association within the where clause needs to use the table name.
Try this:
Software.includes(:user).where(users: { subscribed: true })
This assumes you'll be using the info on the user elsewhere, i.e. in your view. If you don't need to access the record, rather just check the user for the query, you can switch includes to joins to improve the query's efficiency:
Software.joins(:user).where(users: { subscribed: true })
It's a separate topic, but there's a good read on it here.
Related
I have a simple task list app that has users and lists on it, the users are managed by Devise, and can create task lists, as well as favorite lists created by other users, or by themself. The relation of ownership between users and lists were easy to establish, but I am having trouble setting up the relation of a user favoriting a list. I envision it being a many-to-many relation after all, a user can favorite many lists and a list can be favorited by many users, this relationship happening on top of another already existing one-to-many relationship of list ownership by a user gave me some pause as to whether this is good practice to do, but I proceeded with my attempt regardless.
Currently I have two models, one for the user, and one for the list, and I tried to create a migration for the favorites by running rails g migration CreateJoinTableFavorites users lists, which resulted in the following migration
class CreateJoinTableFavorites < ActiveRecord::Migration[5.2]
def change
create_join_table :users, :lists do |t|
t.index [:user_id, :list_id] <-- I uncommented this line
# t.index [:list_id, :user_id]
t.timestamps <-- I added this line
end
end
end
I thought this would create a table named "Favorites" that would automatically link users and lists, but instead it created a table called "lists_users". Now I am stuck as to what to do next. I have read that I need to create a model for this join table, but I don't know how to go about doing that. What command do I run? rails g model Favorites? rails g model ListsUsers? do I also inform the fields I want to add such as rails g model Favorites user_id:integer list_id:integer, or is there another better way to do it such as perhaps rails g model Favorites user:references list:references? What's the best practice here
Beyond that, I have added a button inside my list#show view for the user to click to add that list to their favorites, and had some trouble routing it. What I did was create a button like this:
<%= button_to 'Add to favorites', add_favorites_path({list_id: #list.id}), method: :post %>
as well as a new route:
post 'add_favorites', to: 'lists#add_favorites'
Though this I managed to have access to the list id and user id in that action, now I don't know how to proceed to create the "favorite" database entry in my lists_users table. To illustrate, I'll paste here my "add_favorite" action
def add_favorites
user_id = current_user.id
list_id = params[:list_id]
#TODO: create the relation in lists_items table
end
I'm aware that I can't get this to work without the model for the join table, but even if I had that model, I haven't had much luck searching for what to do within the controller to create that relation. Anyway, my models are as follows:
class List < ApplicationRecord
belongs_to :user
has_many :users, through: :lists_users
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :lists
has_many :lists, through: :lists_users
end
So to summarize, I am aware that I am missing a model for the join table, and would like a step-by-step as to how to create it, what name to give it, etc, as well as how to proceed within my action in my controller to create a new favorite entry
There are two ways to create a many-to-many relation in Rails. What you're doing seems to conflate the two, which I suspect is the source of your problem.
Briefly, the two methods are:
1) has_many :other_models, through: :relation or
2) has_and_belongs_to_many :other_models
The main difference being that the "has_many through" method expects the join table to be a separate model which can be handled independently of this relationship if need be, while the "has_and_belongs_to_many" method does not require the join table to have a corresponding model. In the latter case, you will not be able to deal with the join table independently. (This makes timestamps on the join table useless, by the way.)
Which method you should go with depends on your use case. The docs summarize the criteria nicely:
The simplest rule of thumb is that you should set up a has_many :through relationship if you need to work with the relationship model as an independent entity. If you don't need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you'll need to remember to create the joining table in the database). (emphasis added)
Now for your question: When you use create_join_table, you're treating it as though you're setting things up for a has_and_belongs_to_many relation. create_join_table will create a table named "#{table1}_#{table2}" with ids pointing to those tables. It alphabetizes them too, which is why you got "lists_users" instead of "users_lists". This is in fact the standard naming convention for rails join tables if you are planning on using has_and_belongs_to_many, and generally shouldn't be renamed.
If you really want to use has_and_belongs_to_many, keep the migration with the create_join_table and just do the following in your models:
# user.rb
class User
has_and_belongs_to_many :lists
end
# list.rb
class List
has_and_belongs_to_many :users
end
And voila. No Favorite model is needed, and rails is smart enough to handle the relationships through the table on its own. Although a bit easier, the downside is, as stated above, that you won't be able to deal with the join table as an independent model. (Again, timestamps on the join table are useless in this case, as Rails won't set them.)
Edit: Since you can't directly touch lists_users, you'd create relationships by setting the lists relation on a user, or by setting the users relation on lists, like so:
def add_favorites
list = List.find(params[:list_id])
current_user.lists << list # creates the corresponding entry in lists_users
# Don't forget to test how this works when the current_user has already favorited a list!
# If you want to prevent that from happening, try
# current_user.lists << list unless current_user.lists.include?(list)
# Alternatively you can do the assignment in reverse:
# list.users << current_user
# Again, because the join table is not an independent model, Rails won't be able to do much to other columns on lists_users out of the box.
# This includes timestamps
end
On the other hand, if you want to use "has_many through", don't use create_join_table. If you're using has_many through, the join table should be thought of almost as an entirely separate model, that just happens to have two foreign keys and tie two other models together in a many-to-many relationship. In this case, you'd do something like:
# migration
class CreateFavorites < ActiveRecord::Migration[5.2]
def change
create_table :favorites do |t|
t.references :list
t.references :user
t.timestamps
end
end
end
# user.rb
class User
has_many :favorites
has_many :lists, through: :favorites
end
# list.rb
class List
has_many :favorites
has_many :users, through: :favorites
end
# favorite.rb
class Favorite
belongs_to :list
belongs_to :user
end
# controller
def add_favorites
# You actually have a Favorite model in this case, while you don't in the other. The Favorite model can be more or less independent of the List and User, and can be given other attributes like timestamps.
# It's the rails methods like `save`, `create`, and `update` that set timestamps, so this will track those for you as any other model.
Favorite.create(list_id: params[:list_id], user: current_user)
end
You might want to reflect on which method to use. Again, this really depends on your use case, and on the criteria above. Personally, when I'm not sure, I prefer the "has_many through" method as it gives you more tools to work with and is generally more flexible.
You may try following :
class User
has_and_belongs_to_many :lists
end
class List
has_and_belongs_to_many :users
end
class CreateUsersAndLists
def change
create_table :users do |t|
# Code
end
create_table :lists do |t|
# Code
end
create_table :users_lists id: false do |t|
t.belongs_to :user, index: true
t.belongs_to :list, index: true
t.boolean :is_favourite
end
end
end
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I want to create a base model Person with some person related attributes like name, address, phone and so on. One Person can be one ore more of the following:
LoginUser with fields for login, password, last_login, ...
CardHolder with fields for card_id, last_entrance, ...
Supplier with just a flag whether or not the person is a supplier
Recipient with just a flag whether or not the person is a recipient
Is there a common sense or best practise design pattern in Ruby on Rails to represent that inheritance? How it should be represented in the model(s) and table structure so that it is possible to check whether a Person is a LoginUser and to access the corresponding fields.
In another project I worked already with STI but in this case this isn't the right pattern.
What you're looking for is a reverse polymorphic association. Polymorphic associations allow you to link one model to many different ones. A reverse polymorphic association allows you to link many models to one single one. They're a little tricky to set up, but once you get the hang of it it's no problem.
In order to accomplish this, you need another model that acts as a go-between for the Person model and each of the different roles. This go-between model is the one that actually has the polymorphic association. Your Person model will has_many that model, and your various role models will has_one of it. You then use :through to make the rest of the necessary associations so your code doesn't know any different. Shazam!
Here's an example of how to do it with the Person and CardHolder models. I'm calling the extra model Role because that seems like an obvious choice:
class Person < ApplicationRecord
has_many :roles
# Reach through the Roles association to get the CardHolders, via polymorphic :rollable.
# Unfortunately, you can't has_one, so you'll have to enforce uniqueness in Role
# with a validation.
has_many :card_holders, through: :roles, source: :rollable, source_type: 'CardHolder'
end
class Role < ApplicationRecord
belongs_to :person
# Here is where our actual polymorphic connection is:
belongs_to :rollable, polymorphic: true
end
class CardHolder < ApplicationRecord
# The other side of the polymorphic connection, with has_one:
has_one :role, as: :rollable
# Get the person via the role, just like the inverse:
has_one :person, through: :role
end
The database setup is like this:
class CreatePeople < ActiveRecord::Migration[5.1]
def change
create_table :people do |t|
t.string :name
# put in whatever other Person columns you need
t.timestamps
end
end
end
class CreateRoles < ActiveRecord::Migration[5.1]
def change
create_table :roles do |t|
t.references :person, index: true
t.references :rollable, polymorphic: true, index: true
t.timestamps
end
end
end
class CreateCardHolders < ActiveRecord::Migration[5.1]
def change
create_table :card_holders do |t|
t.integer :card_id
t.datetime :last_entrance
# put in whatever other columns you need
t.timestamps
end
end
end
Using it is quite simple:
> p = Person.create(name: "Sven Reuter")
# directly add a card holder
> p.card_holders << CardHolder.create(card_id: 1, last_entrance: Time.current)
# build a role instead
> p.roles.build(rollable: CardHolder.new(card_id: 2, last_entrance: Time.current)
# get all of the roles
> p.roles
I would go with Person table and the PersonAttributes table that is a union of all the attributes the person might have. PersonAttributes might use STI if applicable, e.g. with LoginUser storing logins and CardHolder referencing Cards.
Clean and simple.
I need to know, if there is a way to deal with different type of users/clients in Rails using a single model.
What I really need to do?
- I need to save a different type of clients in my database. So, I have this migration:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.string :name # Person name if "Personal", Company name if type "Company"
t.string :nif # fillable only if type is Company
t.string :identity_card_number # fillable only if type is Personal
t.integer :client_type # Type can be Personal or Company
t.references :company, index: true # if type is personal, it can belong to a company
t.timestamps null: false
end
end
end
Then I create this model
class Client < ActiveRecord::Base
has_many :employees, class_name: 'Client', foreign_key: 'company_id'
belongs_to :company, class_name: 'Client'
end
Note: A personal account can belong to a company or not.
Based on your experience, am I doing this in the right way? There are another way to do that?
EDIT:
Hi #manfergo25,
Whit this I have another question. "Company" and "Personal" are both "Clients Account", in that way, both must be able to buy services.
If I need to associent the client with the service, can I do this?
class Personal < Account
has_many :services
end
and
class Service < ...
belongs_to :account
end
??
The right way is Single Table Inheritance (STI) as Sontya say.
class Account < ActiveRecord::Base
end
Then,
class Client < Account
end
class Provider < Account
end
You only have to add a type column in 'Account' to contain a string representing the type of the stored object.
For example in a controller you could do this:
account = Client.find(params[:autocomplete_client])
params[:service][:account_id] = account.id
#service = Service.new(params[:service])
You can use STI(Single Table Inheritance)
class Account < ActiveRecord::Base
end
class Company < Account
has_many :services, :dependent => :destroy
end
class Personal < Account
has_many :services, :dependent => :destroy
end
class Service < ActiveRecord::Base
belongs_to: personal
belongs_to: company
end
With the above definition, a personal and company should be able to buy services.
and you should be able to call
#company.services # it will return you the number of services of company
#personal.services # it will return you the number of services of personal
Personally, I think the way you have suggested in your question is the way I'd do it.
While Single Table Inheritance is intellectually better modelling -- I've found STI to be a bit hard to work with and sometimes unreliable in Rails. And probably won't improve the cleanliness or conciseness of your code all that much in the end anyway. STI is good to keep in mind as an option, if you find the OP approach is not working well as far as allowing you to write clear concise code and it seems STI could work out better.
But I'd start without STI, with just the one Client class, as you've outlined in your question. If you later add STI, you'd still have the Client class, you'd just have sub-classes for, say, PersonalClient and CompanyClient. It won't be that hard to switch to STI later if you want to, although it might require some minor db schema alterations.
But I don't think it'll get you enough benefit to justify the added complexity, in an area of Rails that has sometimes had some rough edges.
Here's some more info about STI and it's plusses and minuses: http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/
I have 3 models in my rails app
class Contact < ActiveRecord::Base
belongs_to :survey, counter_cache: :contact_count
belongs_to :voter
has_many :contact_attempts
end
class Survey < ActiveRecord::Base
has_many :questions
has_many :contacts
end
class Voter < ActiveRecord::Base
has_many :contacts
end
the Contact consists of the voter_id and a survey_id. The Logic of my app is that a there can only be one contact for a voter in any given survey.
right now I am using the following code to enforce this logic. I query the contacts table for records matching the given voter_id and survey_id. if does not exist then it is created. otherwise it does nothing.
if !Contact.exists?(:survey_id => survey, :voter_id => voter)
c = Contact.new
c.survey_id = survey
c.voter_id = voter
c.save
end
Obviously this requires a select and a insert query to create 1 potential contact. When I am adding potentially thousands of contacts at once.
Right now I'm using Resque to allow this run in the background and away from the ui thread. What can I do to speed this up, and make it more efficient?
You can do the following:
Contact.where(survey_id: survey,voter_id: voter).first_or_create
You should add first a database index to force this condition at the lowest level as possible:
add_index :contacts, [:voter_id, :survey_id], unique: true
Then you should add an uniqueness validation at an ActiveRecord level:
validates_uniqueness_of :voter_id, scope: [:survey_id]
Then contact.save will return false if a contact exists for a specified voter and survey.
UPDATE: If you create the index, then the uniqueness validation will run pretty fast.
See if those links can help you.
Those links are for rails 4.0.2, but you can change in the api docks
From the apidock: first_or_create, find_or_create_by
From the Rails Guide: find-or-create-by
It would be better if you let MySQL to handle it.
Create a migration and add a composite unique key to survey_id, voter_id
add_index :contact, [:survey_id, :voter_id], :unique=> true
Now
Contact.create(:survey_id=>survey, :voter_id=>voter_id)
Will create new record only if there is no duplicates.
I'm modelling a scenario with Users and Tools, where a Tool is owned by one User but can be used by many Users including to one owning it.
I was thinking about adding an owner_id column to Tools and say it has_many Users or by adding a new relationsship table.
I'm really new to Rails and I have problems setting up the right associations in the models though, maybe you can point me in the right direction?
Thank you very much.
Your should add owner_id to the Tools table.
Associations will be like that.
class User < ActiveRecord::Base
has_and_belongs_to_many :tools
end
class Tool < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :owner, :class_name => 'User'
end
You'll need a tools_users table in order to use habtm-association. Generate a migration and create a table with option id: false and two columns user_id and tool_id:
class CreateToolsUsersTable < ActiveRecordMigration
def change
create_table :tools_users, id: false do |t|
t.integer :tool_id
t.integer :user_id
end
end
end
After that you can call something like #user.tools or #user.owner
Read more there
User has many tools
Tool belongs to user in owner
Tool has many users
is what I would do.
I'm not sure about the wording because I don't use Active Record but this is how it works in other orms