ROR: counter cache, has_many thougth, delete with nested params - ruby-on-rails

I have next models:
class Document < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy
has_many :sub_roles, through: :sub_roles_documents,class_name: '::SubRole'
end
class SubRole < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy
has_many :documents, through: :sub_roles_documents, class_name: '::Document'
end
class SubRolesDocument < ActiveRecord::Base
belongs_to :sub_role, counter_cache: :documents_count, touch: true
belongs_to :document, counter_cache: :sub_roles_count
end
And when I delete sub_roles for some documents using nested parameters counter cache sub_roles_count doesn't change, but when I add new sub_roles to documents all work fine.
If I directly remove sub_roles of documents documents.sub_roles.delete(specific_sub_role) - it's work fine too.
What is best way in my case?

I figured out a problem, all wrote in documentation:
This option can be used to configure a custom named :counter_cache. You only need this option when you customized the name of your :counter_cache on the belongs_to association.
In my case I must write next:
class Document < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy, counter_cache: :documents_count
has_many :sub_roles, through: :sub_roles_documents,class_name: '::SubRole'
end
Because I use the customize name for counter cache.

Related

Multiple associations in the has_many "through" model

I have a Products & Parts model which would each have multiple uploads, which are also polymorphic. Is it possible for me to have a single ItemUpload model to handle the association between the Products/Parts and Uploads, or do they need to be separate? I'd try myself just to see, but don't want to cause any potential headaches down the line! Note that I'm aware I need to do the source: and source_type: stuff to clean up the polymorphic association with has_many, but would like to clarify this point first before proceeding. Current models:
class Product < ApplicationRecord
has_many :uploads, as: :uploadable, dependent: :destroy
end
class Part < ApplicationRecord
has_many :uploads, as: :uploadable, dependent: :destroy
end
class Upload < ApplicationRecord
belongs_to :uploadable, polymorphic: true
end
What I would ideally like:
Class ItemUpload < ApplicationRecord
belongs_to :product, optional: true
belongs_to :part, optional: true
belongs_to :upload
end
Is that ok or would I need a separate ProductUpload and PartUpload model?
I would have thought your associations would look more like:
class Product < ApplicationRecord
has_many :item_uploads, as: :itemable, dependent: :destroy
has_many :uploads, through: :item_uploads
end
class Part < ApplicationRecord
has_many :item_uploads, as: :itemable, dependent: :destroy
has_many :uploads, through: :item_uploads
end
class Upload < ApplicationRecord
has_many :item_uploads
has_many :products, through: :item_uploads, source: :itemable, source_type: 'Product'
has_many :parts, through: :item_uploads, source: :itemable, source_type: 'Part'
end
Class ItemUpload < ApplicationRecord
belongs_to :itemable, polymorphic: true
belongs_to :upload
end
That should allow you to do:
product.uploads
part.uploads
upload.products
upload.parts
BTW, in reference to the link you provided:
Upload ≈ User
ItemUpload ≈ Membership
Product, Part ≈ Project, Group
The above follows the pattern in the linked article.

Many-to-many association with join table and multiple aliases

I'm working on a geocaching application where users can create a new cache or visit an existing one. Here are the models:
class User < ApplicationRecord
has_many :usercaches
has_many :visited_caches, source: :caches, through: :usercaches
has_many :created_caches, class_name: :caches
end
class Cache < ApplicationRecord
has_many :usercaches
has_many :visitors, source: :users, through: :usercaches
end
class Usercache < ApplicationRecord
belongs_to :user
belongs_to :cache
end
The join table looks the way it does because I've been trying to eliminate any potential errors related to capitalization or pluralization. Whenever I create a User in Rails console and try to look at new_user.visited_caches, I get the following error:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not
find the source association(s) :caches in model Usercache. Try
'has_many :visited_caches, :through => :usercaches, :source =>
'. Is it one of user or cache?
When I rearrange the association as suggested, however, the error remains the same. Is there some small detail I'm missing, or am I working with a completely incorrect paradigm for what I want to accomplish?
source must be provided in a singular form (the documentation for has_many provides an example for it):
class User < ApplicationRecord
has_many :usercaches
has_many :visited_caches, source: :cache, through: :usercaches
has_many :created_caches, class_name: :caches
end
class Cache < ApplicationRecord
has_many :usercaches
has_many :visitors, source: :user, through: :usercaches
end
class Usercache < ApplicationRecord
belongs_to :user
belongs_to :cache
end

How do I handle creating a join model when neither associated model has been created?

In a simplified set up I have 4 models.
App :has_many Environments,Variables
Environment :has_many VariableValues
Variable :has_many VariableValues
VariableValue :belongs_to Environment,Variable
In my react app, you create an App, then you can create Envrionments and Variables independently. Each variable can have a value for each environment, so I create a VariableValue that has the actual value, and then an environment_id and variable_id that associates the variable value to an environment. When saving this, and it all saves at once, I do not have a environment_id to give to the join model, since neither the variables or environments will be persisted before the VariableValues are created. I am doing this entire save of the App with accepts _nested_attributes. It may not be possible to do this, so I'm curious how someone else has handled this situation.
Thanks!
UPDATE:
Here are my models
class Workspace < ApplicationRecord
has_many :workspace_envs, inverse_of: :workspace
has_many :workspace_variables, inverse_of: :workspace
accepts_nested_attributes_for :workspace_envs, :workspace_variables, allow_destroy: true
end
class WorkspaceEnv < ApplicationRecord
acts_as_paranoid
belongs_to :workspace, inverse_of: :workspace_envs
has_many :workspace_env_variable_values, inverse_of: :workspace_env
has_many :workspace_variable_values, inverse_of: :workspace_env, through: :workspace_env_variable_values
has_many :workspace_variables, inverse_of: :workspace_envs, through: :workspace_env_variable_values
end
class WorkspaceVariable < ApplicationRecord
belongs_to :workspace, inverse_of: :workspace_variables
has_many :workspace_env_variable_values, inverse_of: :workspace_variable
has_many :workspace_variable_values, inverse_of: :workspace_variable, through: :workspace_env_variable_values, dependent: :destroy
has_many :workspace_envs, inverse_of: :workspace_variables, through: :workspace_env_variable_values
accepts_nested_attributes_for :workspace_variable_values, allow_destroy: true
end
class WorkspaceVariableValue < ApplicationRecord
include VariableValue
has_one :workspace_env_variable_value, inverse_of: :workspace_variable_value
has_one :workspace_variable, inverse_of: :workspace_variable_values, through: :workspace_env_variable_value
has_one :workspace_env, inverse_of: :workspace_variable_values, through: :workspace_env_variable_value
accepts_nested_attributes_for :workspace_env
end
class WorkspaceEnvVariableValue < ApplicationRecord
belongs_to :workspace_env, inverse_of: :workspace_env_variable_values
belongs_to :workspace_variable_value, inverse_of: :workspace_env_variable_value
belongs_to :workspace_variable, inverse_of: :workspace_env_variable_values
end
The important thing here is that all of these models are created at the same time, not even the Workspace exists, it is all on big save, in one big transaction, my current solution is to build the workspace_variables and workspace_envs and then to associated the workspace_envs with the workspace_env_variables_values based on their index.
:belongs_to can be optional, us just append ',optional: true' and you can save record into your db without any problems. but be careful about it, i.e. you leave your object in invalid state (from business logic point of view), don't forget to update saved records and add foreign id values.
class VariableValue < ApplicationRecord
belongs_to :environment, optional: true
belongs_to :variable, optional: true

Is the first has_many redundant? - Ruby on Rails Active records associations

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

many to many relation by Users and Files and ownership of it in rails

How can I add ownership in many to many relationships?
For example like this models.
class User < ActiveRecord::Base
has_many :editabilities, dependent: :destroy
has_many :files, through: :editabilities
end
class File < ActiveRecord::Base
has_many :editabilities, dependent: :destroy
has_many :users, through: :editabilities
end
class Editabilities < ActiveRecord::Base
belongs_to :user
belongs_to :file
end
And I want to add a one-to-many relationship to User-and-Files.
At first I thought it is best to add owner boolean column to Editabilities, but I have no idea how to handle it.
Secondly I thought if I make a new junction model Ownerships, then I can handle it same way as Editabilities. But I've got a uninitialized constant User::Ownership when I tried it with code like this.
class User < ActiveRecord::Base
has_many :editabilities, dependent: :destroy
has_many :ownerships, dependent: :destroy
has_many :files, through: :editabilities
has_many :owned_files, through: :ownerships, source: :file
end
class File < ActiveRecord::Base
has_many :editabilities, dependent: :destroy
has_many :ownerships, dependent: :destroy
has_many :users, through: :editabilities
has_one :owner, through: :ownerships, source: :user
end
class Editabilities < ActiveRecord::Base
belongs_to :user
belongs_to :file
end
class Ownerships < ActiveReord::Base
belongs_to :user
belongs_to :file
end
How can I implement a feature like this?
The only problems I see here are the classes Editabilities and Ownerships. By Rails convention model class names should be singular, not plural.
class Editability < ActiveRecord::Base
belongs_to :user
belongs_to :file
end
class Ownership < ActiveReord::Base
belongs_to :user
belongs_to :file
end
One way to quickly check your class names is by checking the result of the classify function:
> "editibilities".classify
=> "Editibility"
> "ownerships".classify
=> "Ownership"
The rest of the associations all look correct.
Your class should be named Ownership and not Ownerships.
ActiveRecord class names are normally singular and table names are plural.
Easier solution seems to add belongs_to association to File model. Because 1 File can have only 1 owner.
class File < ActiveRecord::Base
...
belongs_to :owner, class_name: 'User'
end
You will need to add owner_id column to files table.

Resources