How to setup self-referential polymorphic relationship in rails? - ruby-on-rails

I have a 3 models
User
Journal
Post
A couple of things can be assumed about these
User has_many :journals
Journal has_many :posts
I would like users to be able to subscribe to other users and specific journals on my site.
I am thinking I need a Polymorphic Subscription model that looks something like this
class CreateSubscriptions < ActiveRecord::Migration
def change
create_table :subscriptions do |t|
t.integer :user_id
t.references :subscribable, :polymorphic=>true
t.timestamps
end
end
end
But here's where I'm stuck. I don't know how to setup the polymorphic relationships in my User model.
I want to be able to get the following things:
#user.watched_users
#user.watched_journals
#user.followers
#journal.followers
Can someone help? The docs are kind sparse on this and I know these can be a real chore to setup.
^_^

While I understand the desire to want to just have the user subscribe to arbitrary objects, in practice, you'll end up dealing with these different subscriptions differently, so at least for now (until you find yourself wanting to subscribe to other objects on the site), just leave them separate (YAGNI).
You can do this through a simple join table for each relationship. the answer on How do I do reflexive self-join relationships in ActiveRecord? leads to a good example in http://railscasts.com/episodes/163-self-referential-association

Related

Complex queries through multiple models in rails

This is going to get kind of long, but I'm self taught, and I'm afraid I'm making some sort of fundamental error that is causing me lots of issues - so I'm going to be really thorough.
The setup (simplified of course - and I had to change the names so if you see a typo, that's not the issue, sorry):
class Deviceapi < ActiveRecord::Base
belongs_to :devices
end
class Device < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :deviceapis
end
class User < ActiveRecord::Base
has_many :templates, dependent: :destroy
has_many :designs, through: :templates
has_many :deviceapis, through: :devices
has_and_belongs_to_many :devices
end
class Template < ActiveRecord::Base
belongs_to :user
has_many :designs, dependent: :destroy
end
class Design < ActiveRecord::Base
belongs_to :template
end
The explanation:
My program receives data from a device - each data entry creates a deviceapi entry in my database. This data is then used by the templates and designs. Users don't have access to the devices (except admins), and no one actually accesses the deviceapis at all.
Now, I want to be able to write queries such as:
class Placement < ActiveRecord::Base
.
.
# This counts the number of api entries associated with the design
self.campaign.user.devices.deviceapis.count
end
Further, admins should be able to restrict users access to devices as desired. Which should indirectly restrict access to deviceapis, since they are only accessible through devices anyway (they don't have any view pages or anything).
Specifically, my schema (simplified) shows:
ActiveRecord::Schema.define(version: ################) do
create_table "deviceapis", force: :cascade do |t|
t.string "api_id"
end
create_table "devices", force: :cascade do |t|
t.string "device_guid"
t.integer "user_ids"
end
create_table "designs", force: :cascade do |t|
t.string "device_id"
end
end
Queries should automatically be restricted to instances where device_guid (device) and api_id (deviceapi) match. Then it should be further restricted by user_ids (device) matching the user_id (the primary key of user). Then even more restricted by the device_id (design) matching the device_id (primary key of device).
Scopes and methods can handle the queries once I get them working, but I can't get the queries to do anything but come up empty or spit out errors. I think I'm needing some join tables or something.
How would I build proper join tables for this? Or is there some other, much more obvious way to do this that I'm not seeing? I've spent a long time on this, and I'm learning a lot in trial by fire, but not really making any progress on this specific issue - and I think it's crippling me in other places.
Thanks for any help!
Here is what I think will be the issue.
If you log this:
Placement.campaign.user.devices you would get some results. ( I don't know the relation in Placement and Campaign so I am just guessing.
In your Console do this: ( Based on the above assumptions )
Placement.campaign.user.devices.class you should get something like this : Device::ActiveRecord_Associations_CollectionProxy or similar.
You will not be able to do this now Placement.campaign.user.devices.deviceapi as Rails don't know which one of the record you need the deviceapi.
If you do something like this Placement.campaign.user.devices.first.deviceapi then you should get result also if you do Placement.campaign.user.devices.first.deviceapi.class you should get something like this Deviceapi or something similar.
To query the number of deviceapi use join with condition.
Something like this User.joins(devices: (:deviceapis) ).where('users.id = ?', current_user.id).count
I assumed you have the current_user available or put some other condition that you think it fits.
Please let me know if this helps.

Choosing between has_many :through and has_and_belongs_to_many association

I’m new to programming and have difficulty determining the model association I need. I already have two tables: Organizations and Users with a 1:many relationship, since an organization has multiple users/members. Not all users are a member of an organization though but that’s not a problem.
# In the organization model:
has_many :users
# In the user model:
belongs_to :organization, inverse_of: :users
# In the users migration file:
t.references :organization, index: true, foreign_key: true
I need to extend this with a moderator function: A user can be a moderator for multiple organizations and an organization can have multiple users as their moderator. So there will then be two separate reasons for a relationship between the user and organization model (user as a member of an organization and user as a moderator of an organization; no need to a member before you can be a moderator).
I read here about the two different possible ways to create the association, namely has_many :through or has_and_belongs_to_many.
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).
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
I'm not sure what is meant by "if you need to work with the relationship model as an independent entity". In my use case is a moderator will have additional rights. The association will, I belief, be used in two ways: 1) displaying the moderators for an organization, and displaying the organizations that a user is the moderator of; and 2) look up if a user has moderator rights to allow him these rights. So I don't belief I will need validations (organization will itself be able to select users as moderator) or extra attributes. I'm not sure about callbacks (don't know exactly what they are).
So in short, which one of the two forms of association is the best association in my use case?
"if you need to work with the relationship model as an independent entity" means you will be able to operate on the Relationship model as any other ActiveRecord model, i.e you can create, save, destroy etc. records or assign various types of attributes to the relationship.
Suppose, for example, that a user could have different types of relationship with the organization (e.g. moderator, employee, stockholder et). Using the :through association, you would have:
class Relationship < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
class User < ActiveRecord::Base
has_many :organizations, through: relationships
end
class Organization < ActiveRecord::Base
has_many :users, through: relationships
end
# schema.rb
ActiveRecord::Schema.define(:version => 20150511223747) do
# . . . .
create_table "relationships", :force => true do |t|
t.string "relationship_type"
t.integer "user_id"
t.integer "organization_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
# things you can do:
org = Organization.first
moderators_or_employees = org.users.where("relationship_type = 'moderator' OR relationship_type = 'employee'")
new_moderators = org.users.where("relationship_type = 'moderator' AND DATE(relationships.created_at) > '2015-01-01'")
These are things you could not do with the has_and_belongs_to_many association since you have no way of querying on relationship type or created_at. Because of these limitations, I have often found that the :through relationship, although a bit more complicated, gives you a lot more flexibility.
My nutshell explanation when people ask:
habtm – only a join table, e.g., two ids
hm:t – a join table with meta data about the join relationship, e.g., two ids and other data like times, counts, etc.

ActiveRecords [something] belongs_to [User] and has_many [Users]

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

many to many relationship in rails 3

I have created 2 models in rails and modified the models classes to add a many to many relationship (with has_and_belongs_to_many)
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :users
end
When I create a User though the web interface, I am not asked to select some categories.
Did I miss somthing ? I read that another table was required but it semmed it was in the case of has_many and not has_and_belongs_to_many statement).
Could you please help ?
I think this is a newby question but...
Thanks a lot,
Regards,
Luc
For HABTM you need a join table called categories_users. Use this migration:
def self.up
create_table :categories_users, :id => false do |t|
t.integer :category_id
t.integer :user_id
end
end
What does your view look like? Rails's scaffolding won't account for many-to-many, so you will need to handle it yourself.
What does your data store look like? If you are using a RDBMS, then for many to many relations you typically need a junction table. Many-to-many is not naturally handled by most (all?) SQL databases.

Correct Model Data Structure? (My 1st Rails App)

I'm about to build my first Ruby on Rails application (and first anything beyond xhtml and css), and I'm looking for some helpful feedback on my model structure.
I've included a mockup to help visual the application.
The only models I'm sure I need so far are:
1. a movie model (to serve as the main model, named movie so URL's will look like, "sitename.com/movies/1-Willy-Wonka") and
2. a user model
Other than those, I don't know what to make the others; like 'filming locations', 'actors', and most importantly, the attributes like 'willy wonka' and 'johnny depp'.
Should I make all of those models, even the attributes? If so, is it possible, and recommended, to let people create models as attributes?
I try to see the app as data created by forms and stored in databases, and with that pov I can't decide how to connect attributes (character <=> actor) to sections (actors, filming locations, artwork, etc.) inside a movie listing; while allowing the attributes themselves to be hyperlinks to related attributes/movies/even sections: clicking on 'steven speilberg' would bring you to a page with movies he's acted in, films he's directed, produced, written, etc.
Thoughts?
I would recommend getting your data model clear first. You can then start with one rails model per database table. This is not always the case, but it's a reasonable place to start.
Let's focus on the movies and actors:
Some assumptions:
movies can have a many-to-many relationship to the other entities
you want to store the character information as part of the relationship between an actor and a movie
You might then model your relationships like this:
# movies.rb
class Movie < ActiveRecord::Base
has_many :roles
has_many :actors, :through => :roles
end
# actor.rb
class Actor < ActiveRecord::Base
has_many :roles
has_many :movies, :through => :roles
end
Normally, you can rely on Rails' magic to handle the join model without creating one yourself. In this case we want to store the character information as attributes of the join model, so we create it explicitly.
# role.rb
class Role < ActiveRecord::Base
belongs_to :movie
belongs_to :actor
end
Start with models Movie, Actor, FilmLocation, and Character. Movie and Actor have a many-to-many relationship(A Movie has many actors, an actor worked on many movies). FileLocation and Movie also are many-to-many. Character(Willy Wonka) and Actor are many-to-many as well.
If this is your first anything as far as web development then I suggest you start with writing a simple web app that just lists movies and allows your to add, edit and delete them. Just store the title, a synopsis and maybe the URL of the movie poster/dvd cover. Then work on adding Actors and associating them with the Movie.
Creating an "IMDB-like" site is not a trivial project. You're going to have a lot complex relationships beyond just associating an actor with a movie. Actors have Roles in Movies. And you might want to get even more abstract than that and say that a Person has Job in a Movie which would allow you to also track things like the Directors, Producers, Key Grips, Casting Directors.
Not only should you work on your data model, but work on a plan about what you want the site to consist of and what order you should create those features in (based on need) and take small steps to get to that final goal.
Further elaboration. The migrations for the models listed above might look like this:
class CreateMovies < ActiveRecord::Migration
def self.up
create_table 'movies' do |t|
t.string 'title', :null => false
t.timestamps
end
end
def self.down
drop_table 'movies'
end
end
class CreateActors < ActiveRecord::Migration
def self.up
create_table 'actors' do |t|
t.string 'first_name', 'last_name', :null => false
t.timestamps
end
end
def self.down
drop_table 'actors'
end
end
The movie_id and actor_id fields below correspond to the belongs_to associations in the Role model above and are the foreign keys that allow the roles table to join actors and movies. As I have proposed modeling it, the character_name is a property of this relationship, and is thus an attribute of the role. It's your call whether you allow NULL in the character_name. I'm leery of NULL, but in this case I've allowed it as one could make the argument that in practice you often wish to store the fact that an actor is in a given movie but don't know or care about the character name.
class CreateRoles < ActiveRecord::Migration
def self.up
create_table 'roles' do |t|
t.integer 'movie_id', 'actor_id', :null => false
t.string 'character_name'
t.timestamps
end
end
def self.down
drop_table 'roles'
end
end
It might be worthwhile to read up on database normalization: http://en.wikipedia.org/wiki/Database_normalization
In general, yes. I like my models to be as granular as possible. This makes things much clearer for someone who isn't familiar with the application and makes it easier to reuse code.
It's helpful to have a firm grasp of ActiveRecord associations before writing a complex application. Make sure you know all of the associations by heart and what they actually generate in terms of SQL tables. Sometimes it can seem like magic, and magic isn't necessarily a good thing. If you know what's behind it things fall in to place.
Also, don't be afraid to scrap everything and start over while experimenting. It's Ruby, so it won't take long to get back to where you were.

Resources