I have a "LineOfBusiness" model which has an irregular pluralization ("lines_of_business" instead of "line_of_businesses").
I've set up my "has_many through" associations and made what I thought were the necessary adjustments for the irregular name, but I'm not sure why I'm having to use the plural form of the IDs in the strong params. The singular "user_ids" works in the params as I expected, but I have to use the plural "lines_of_business_ids" to get them to work properly. It seems like something small is off, but I can't figure it out.
class Question < ApplicationRecord
has_many :user_questions, dependent: :destroy
has_many :users, through: :user_questions
has_many :line_of_business_questions, dependent: :destroy
has_many :lines_of_business, through: :line_of_business_questions, :source => :line_of_business
end
class User < ApplicationRecord
has_many :user_questions, dependent: :destroy
has_many :questions, through: :user_questions
end
class LineOfBusiness < ApplicationRecord
self.table_name = "lines_of_business"
has_many :line_of_business_questions, dependent: :destroy
has_many :questions, through: :line_of_business_questions
end
def question_params
params.require(:question).permit(:name, :input_type, user_ids: [], lines_of_business_ids: [])
end
Related
I'm looking for a better way to query Users from 2 different Models used in a polymorphic association. Here is the setup
class Schedule < ApplicationRecord
belongs_to :announcement
has_many :targets, dependent: :destroy
has_many :lists, through: :targets, source: :target, source_type: 'List'
has_many :accounts, through: :targets, source: :target, source_type: 'Account'
end
class Target < ApplicationRecord
# belongs_to :announcement
belongs_to :schedule
belongs_to :target, polymorphic: true
delegate :announcement, to: :schedule
end
class List < ApplicationRecord
belongs_to :account
has_many :targets, as: :target, dependent: :destroy
has_many :lists_users
has_many :users, through: :lists_users
end
class Account < ApplicationRecord
has_many :announcements, dependent: :destroy
has_many :targets, as: :target, dependent: :destroy
has_many :users, dependent: :destroy
end
At the moment I'm solving this by creating a method inside the Schedule model that grabs Users this way:
def subscribers
targets.map(&:target).map(&:users).flatten.uniq
end
I looked at something similar with this question, but didn't seem to solve it.
I would do that like this:
class Schedule < ApplicationRecord
def subscribers
# fetch all associated user IDs
lists_user_ids = lists.joins(:lists_users).distinct.pluck("lists_users.user_id")
accounts_user_ids = accounts.joins(:users).distinct.pluck("users.id")
user_ids = (lists_user_ids + accounts_user_ids).uniq
# fetch users by IDs
User.where(id: user_ids)
end
end
Rails 5.1.2
Ruby 2.5.3
I understand there are multiple ways to impliment this relationship, however, this question is more about why the following doesn't work rather than solving a real world problem.
has_many setup
class Subscriber < ApplicationRecord
has_many :subscriptions, inverse_of: :subscriber
has_many :promotions, through: :subscriptions, inverse_of: :subscriptions
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :promotions
end
class Subscription < ApplicationRecord
belongs_to :subscriber, inverse_of: :subscriptions
belongs_to :promotion, inverse_of: :subscriptions
end
class Promotion < ApplicationRecord
has_many :subscriptions, inverse_of: :promotion
has_many :subscribers, through: :subscriptions, inverse_of: :subscriptions
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :subscribers
end
In the above Subscriber model which is setup to use has_many relationships following would work:
s = Subscriber.new
s.subscriptions.build
# OR
s.promotions.build
Following that, I would expect Subscriber to behave the same way with has_one relationships
has_one setup
class Subscriber < ApplicationRecord
has_one :subscription, inverse_of: :subscriber
has_one :promotion, through: :subscription, inverse_of: :subscriptions
accepts_nested_attributes_for :subscription
accepts_nested_attributes_for :promotion
end
class Subscription < ApplicationRecord
belongs_to :subscriber, inverse_of: :subscription
belongs_to :promotion, inverse_of: :subscriptions
end
class Promotion < ApplicationRecord
has_many :subscriptions, inverse_of: :promotion
has_many :subscribers, through: :subscriptions, inverse_of: :subscription
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :subscribers
end
However, attempting to build the nested promotion association with the equivalent has_one build methods results in a NoMethodError (undefined method 'build_promotion' for #<Subscriber:0x00007f9042cbd7c8>) error
s = Subscriber.new
s.build_promotion
However, this does work:
s = Subscriber.new
s.build_subscription
I feel it's logical that one should expect to build nested has_one relationships in the same way one builds has_many.
Is this a bug or by design?
Checking the code, when you call has_one, it creates the build_, create_ and create_..! methods ONLY if the reflection is "constructable"
https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activerecord/lib/active_record/associations/builder/singular_association.rb#L16
define_constructors(mixin, name) if reflection.constructable?
Now, checking the constructable? method, it returns the result of calculate_constructable https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L452
And for the HasOne class, it returns false if you use the :through option https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L723
def calculate_constructable(macro, options)
!options[:through]
end
So, I'd say it's not a bug, it's made like that by design. I don't know the reason though, maybe it feels logical but I guess there's some things to consider that are not that simple.
I have three models. A collection has many searches through items.
When a collection is destroyed, I'd like its items to be destroyed, but searches to be nullified, since they are still valid objects on their own.
Can this be done?
Here are my models:
class Collection < ApplicationRecord
has_many :searches, through: :items
has_many :items
has_many :searches, through: :suggestions
has_many :suggestions
end
class Item < ApplicationRecord
belongs_to :collection
belongs_to :search
end
class Search < ApplicationRecord
has_many :collections, through: :items
has_many :items
has_many :collections, through: :suggestions
has_many :suggestions
end
You can just delete the items, just add dependent: :destroy to has_many :items.
class Collection < ApplicationRecord
has_many :searches, through: :items
has_many :items, dependent: :destroy
end
After destroying a collection, the item is destroyed but the search will remain.
second solution
You could even apply dependent: :nullify to has_many :searches - getting the same result.
class Collection < ApplicationRecord
has_many :searches, through: :items, dependent: :nullify
has_many :items
end
From the docs:
collection.delete(object, …)
Removes one or more objects from the collection by setting their foreign keys to NULL. Objects will be in addition destroyed if they're associated with dependent: :destroy, and deleted if they're associated with dependent: :delete_all.
If the :through option is used, then the join records are deleted (rather than nullified) by default, but you can specify dependent: :destroy or dependent: :nullify to override this.
I am a newbie in Rails and have issues with Ransack:
This is model Project
class Project < ApplicationRecord
searchkick
belongs_to :company
belongs_to :m_category
has_many :project_industries, dependent: :destroy
has_many :m_industries, through: :project_industries
end
This is model Industry:
class Industry < ApplicationRecord
include M
belongs_to :m_industry_category
has_many :project_industries, dependent: :destroy, foreign_key: :industry_id
has_many :projects, through: :project_industries
end
And this is model IndustryCategory:
class IndustryCategory < ApplicationRecord
has_many :industries, dependent: :destroy,
foreign_key: :industry_category_id
has_many :projects, through: :industries
end
Now, I want to search the Project by IndustryCategory but I don't know how. please help me!! tks
You can use something like this
#industrty_category = IndustryCategory.find(params[:id])
#project = #industry_category.projects.all
Rails 4.2 newbie:
2 Questions;
1) Is the first has_many redundant? Since its name is a plural of Save Class?
can I have only:
has_many :savers, through: :saves, source: :saver
Or even better;
has_many :savers, through: :saves
If the answer is yes, where can I set "dependent: :destroy"?
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, class_name: "Save", foreign_key: "saver_id", dependent: :destroy
has_many :savers, through: :saves, source: :saver
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
2) This is the typical blog model, where user can 'save' posts posted by another user to their timeline. Does this model make use best practices? Specially in db performance, doing a Join to get posts saved by a User. The 'Save' table that will have 100MM rows?
Lets first alter your example a bit to make the naming less confusing:
class User
has_many :bookmarks
has_many :posts, through: :bookmarks
end
class Post
has_many :bookmarks
has_many :users, through: :bookmarks
end
class Bookmark
belongs_to :user
belongs_to :post
end
Lets have a look at the query generated when we do #user.posts
irb(main):009:0> #user.posts
Post Load (0.2ms) SELECT "posts".* FROM "posts" INNER JOIN "bookmarks" ON "posts"."id" = "bookmarks"."post_id" WHERE "bookmarks"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Now lets comment out has_many :bookmarks and reload:
class User
# has_many :bookmarks
has_many :posts, through: :bookmarks
end
irb(main):005:0> #user.posts
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :bookmarks in model User
So no, the first has_many is not redundant - in fact its the very core of how has_many through: works. You setup a shortcut of sorts through another relation.
Note in has_many :posts, through: :bookmarks :bookmarks is the name relation we are joining through. Not the table which contains the joins.
To fix your original code you would need to do:
class Post < ActiveRecord::Base
has_many :saves, dependent: :destroy
has_many :savers, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
belongs_to :post # A join table with only one relation is pretty worthless.
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts
has_many :saves, dependent: :destroy
has_many :posts, through: :saves
end
Note that you don't need half the junk - if you have has_many :savers, through: :saves ActiveRecord will look for the relation saver by itself. Also you only want to use dependent: destroy on the join model - not on the post relation as that would remove all the posts a user has "saved" - even those written by others!
Teaching Rails myself, I want to learn the professional way to use the framework and following Rails guidelines best practices. That's not easy, because I usually find answers that 'just works'
I'll try to answer myself and maybe it could be useful for Rails Newbies:
Using has_many through, association, Rails firstly infers the association by looking at the foreign key of the form <class>_id where <class> is the lowercase of the class name, in this example; 'save_id'.
So, if we have the column name 'save_id', we will have the following simplified model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :savers, class_name: "User"
validates :save_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end