We have a user model and feature model:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_features
has_many :features, through: :user_features
end
#app/models/user_feature.rb
Class UserFeature < ActiveRecord::Base
belongs_to :user
belongs_to :feature
end
#app/models/feature.rb
Class Feature < ActiveRecord::Base
has_many :user_features
has_many :users, through: :user_features
end
Features can be added and removed from a user's account
The typical way to do this will be to add / remove records from the user_features collection. However, we wanted to store a "history" of all the features a user has added / removed from their account over time
My question is: how would I store the user_features & indicate which features have been added & which removed?
It's very similar to versioning I think
PaperTrail works great for versioning / tracking the history of model changes and also supports has_many :through associations.
Check out the docs and see if it's what you're looking for or if it's too much for your purposes.
You should add an active field to your UserFeatures model and scope it in your user model (not tested):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_features
has_many :features, through: :user_features
has_many :active_user_features, -> { where(active: true) }, class_name: 'UserFeature'
has_many :active_features, through: :active_user_features
end
For deletion of packages, you should just set the active field to false
Related
I have a question on a platform I'm developing in Ruby on Rails 5.2.
I have an Owner model which is the owner of properties/property. The owner will post a property so that users (in this case roomates) can share the same property/house/department, etc.
I have Owners and I have Users (both tables are created using devise):
Owner.rb:
class Owner < ApplicationRecord
has_many :properties
end
User.rb:
class User < ApplicationRecord
#Theres nothing here (yet)
end
This is where the magic happens. Property.rb:
class Property < ApplicationRecord
belongs_to :owner
has_many :amenities
has_many :services
accepts_nested_attributes_for :amenities
accepts_nested_attributes_for :services
mount_uploaders :pictures, PropertypictureUploader
validates :amenities, :services, presence: true
scope :latest, -> { order created_at: :desc }
end
How can multiple users share a property? I'm aware that it will have a many-to-many association but I'm a bit confused how to connect these relationships so when the owner posts a property it will display something like:
Property available for: 3 users
And then begin to limit users until it completes the amount of users available.
This sounds like your average many to many assocation:
class User < ApplicationRecord
has_many :tenancies, foreign_key: :tenant_id
has_many :properties, through: :tenancies
end
class Tenancy < ApplicationRecord
belongs_to :tenant, class_name: 'User'
belongs_to :property
end
class Property < ApplicationRecord
has_many :tenancies
has_many :tenants, through: :tenancies
def availablity
# or whatever attribute you have that defines the maximum number
max_tenants - tenancies.count
end
end
You can restrict the number of tenants with a custom validation.
You can use a join table, called users_properties. This table will have a property_id and user_id. You'll then have the following in your properties model:
has_many :users_properties
has_many :users, through: :users_properties
Read more about it here https://guides.rubyonrails.org/association_basics.html
Assuming i have 3 models associated to each other:
class Farm < ApplicationRecord
has_many :horses
has_many :events
end
class Horse < ApplicationRecord
belongs_to :farm
has_many :events_horses, class_name: 'Event::EventsHorse'
has_many :events, through: :events_horses, source: :event, dependent: :destroy
end
class Event
belongs_to :farm
has_many :events_horses, class_name: 'Event::EventsHorse'
has_many :horses, through: :events_horses, source: :horse, dependent: :destroy
end
class Event::EventsHorse < ApplicationRecord
self.table_name = "events_horses"
belongs_to :horse
belongs_to :event
audited associated_with: :event, except: [:id, :event_id]
end
How to guarantee that each of the Horse belongs to same Farm as event? Possible solution is using custom validation, but i was wondering if there is some other way. I have few other models like Horse, so it force me to do custom validation method to each of them.
class Event
...
validate :horses_belongs_to_farm
private
def horses_belongs_to_farm
horses.all? {|h| h.farm_id == farm_id}
end
end
I think the model you are using is setting up too many id's between the tables that require consistency checking.
If you set the model up this way, then you don't need to validate that a horse's farm and event are consistent since the data ensures it:
class Farm < ApplicationRecord
has_many :horses
has_many :events
end
class Horse < ApplicationRecord
belongs_to :farm
has_many :events, through: :farm
end
class Event < ApplicationRecord
belongs_to :farm
has_many :horses, through: :farm
end
If you need efficient access to horses from events or events from horses, you can use joins. This gives some simplicity, clarity, and consistency.
You should also have a look at Choosing Between has_many_through and has_and_belongs_to_many.
[Edit based upon updated question and comments] Now that your model and question are a little more clear, my hunch is that putting the validation in the Event model causes redundant validations. Since your intent is to make sure that, in a given event, the horse and farm are consistent, I would put the validation in EventsHorses:
class Event::EventsHorse < ApplicationRecord
...
validate :horse_belongs_to_farm
private
def horse_belongs_to_farm
horse.farm_id == event.farm_id
end
end
As an aside, thy do you have Event::EventsHorse rather than simply have a separate model for EventsHorse?
I'm still learning how to use has_many and has_many through relationships effectly. I am currently building a system where I would like users to be able to access certain maps that they are added to.
The map model is what I need the user to be able to access if they are apart of a certain group.
class Map < ApplicationRecord
has_many :rows
has_many :mapgroups
has_many :groups, through: :mapgroups
end
Since a user can belong to many groups I have a has_many through relationship
class Usergroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
class User < ApplicationRecord
has_many :usergroups
has_many :groups, through: :usergroups
end
class Group < ApplicationRecord
has_many :usergroups
has_many :users, through: :usergroups
has_many :mapgroups
has_many :maps, through: :mapgroups
end
I thought about making a mapgroup model to take care of this but, so far, I am not so sure this is going to work.
class Mapgroup < ApplicationRecord
belongs_to :map
belongs_to :group
end
I am looking for a method to check to see what groups the user is apart of and then, based on those groups, give the user access to the corresponding maps. Am I on the right track with the relationships? How could I do this?
If you want to use MapGroup model only for keeping users an map connected (ModelGroup has only foreign keys on Group and Map) it's not the best approach. In this case it's better to opt for has_and_belongs_to_many assosiation. It will let you to assosiate Groups and Maps without creating useless model.
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model.
class Group < ApplicationRecord
...
has_and_belongs_to_many :maps
end
class Map < ApplicationRecord
...
has_and_belongs_to_many :groups
end
I have three models:
class User < ApplicationRecord
has_many :game_accounts
has_many :favorite_game_accounts, through: :game_account_favorites, source: :game_account
end
class GameAccount < ApplicationRecord
belongs_to :user
has_many :favorite_users, through: :game_account_favorites, source: :user
end
class GameAccountFavorite < ApplicationRecord
belongs_to :user
belongs_to :game_account
validates_presence_of :user, :game_account
validates_uniqueness_of :user, scope: :game_account_id
end
This means that User can have his own GameAccounts and other Users can add them to favorites.
I have added scope in order to prevent one user to have multiple favorites of the same GameAccount. However, there is one problem. User can add to favorite his own GameAccount. How to prevent user adding his own GameAccount to favorites?
I'm not sure that there is any built-in Rails validation for you case, so I'd suggest writing your own custom one.
In your particular case, you can verify on GameAccountFavorite instance, that game_account.user_id isn't equal to user.id.
There's plenty of ways of performing custom validation in Rails
I have models Workout and User which related as many to many through model
UserWorkout. And model UserWorkout has attribute :is_creator, which show what user was the creator. But Workout should have only one creator. What is the best way to add such validation?
class Workout < ActiveRecord::Base
has_many :user_workouts, inverse_of: :workout, dependent: :destroy
has_many :participants, through: :user_workouts, source: :user
def creator
participants.where(user_workouts: { is_creator: true }).order('user_workouts.created_at ASC').first
end
end
class UserWorkout < ActiveRecord::Base
belongs_to :user
belongs_to :workout
end
class User < ActiveRecord::Base
has_many :user_workouts, inverse_of: :user, dependent: :destroy
has_many :workouts, through: :user_workouts
end
Depending on your DBMS, you could add a filtered/partial index on workout_id where is_creator = true
On the active record level, you can add a custom validation
class UserWorkout
validate :workout_has_only_one_creator
private
def workout_has_only_one_creator
if self.class.find_by(workout_id: workout_id, is_creator: true)
errors.add(:is_creator, 'can only have one creator')
end
end
First of all there is a design flaw in your DB structure. I thinkis_creator should not be in UserWorkout . It is a responsibility of Workout
In other words Workout can be created by a user and a User can create many Workout so it's a one-many relation between User and Workout
Keep a created_by_id in Workout and add a association in it. It will make lot of things easier and simpler.
class Workout < ActiveRecord::Base
has_many :user_workouts, inverse_of: :workout, dependent: :destroy
has_many :participants, through: :user_workouts, source: :user
belongs_to :creator , class_name: "User", foreign_key: "created_by_id"
end
and there wont be any need to check the uniqueness as it's the single column in Workout
Now you don't need a complex query like every time you need to find the creator of a workout. It's a simple belongs_to association. Everything will be taken care by rails :)