Complex queries through multiple models in rails - ruby-on-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.

Related

Modeling user table for single database multiple application

Say we have 3 different applications - serviceapp, subscriptionapp, ecomapp, all written in ruby on rails and uses the same database in backend and tables in the backend. So the user table for all these three applications are same. If a user is part of serviceapp using the same email and credentials he can login into subscriptionapp or ecomapp and vice versa.
The reason behind choosing same user table and other table for all the application is puerly business perspective - same single crm and ticketing system for sales and cdm team to track everything. Devise is being used in all three applications along with LDAP so login and signup works fine without any issue.
Problem:
Till now users' last_login_at is a single column so we really can't tell which app he last logged in at. But now we have to start logging these details separately, like when did he last login at serviceapp, ecomapp, subscription app separetly.
Also we are starting to use a new crm of one particular app - subscriptionapp and for the clients(users) of that particular app we have to store extra information like unq_id from crm and so on.
My intial thought is to add these columns in the user table itself. But in the future we might add few extra information to user table which are app specific. Hence adding it to the main user table won't be a good idea for this. How shall I proceed in this case? I though of creating three different tables like subscriptionapp_client, ecomapp_client, serviceapp_client had associating them with the user table like user has_one ***_client.
If the association is present like if user.subscriptionapp_client.present? he is a client of that app and we can store the last login at, crm_uniq_id and all in there in that table itself.
Is there anyother good approach that might fit the problem here? I am reading about MTI but it looks like it won't solve the problem.
Single table inheritance with JSON.
class CreateClientAccount < ActiveRecord::Migration[5.2]
def change
create_table :client_accounts do |t|
t.references :user
t.string :uid # the unique id on the client application
t.string :type
t.integer :sign_in_count
t.datetime :last_sign_in_at
t.jsonb :metadata
t.timestamps
end
add_index :client_accounts, [:user_id, :type], unique: true
end
end
class User
has_many :client_accounts
has_one :service_account, class_name: 'ServiceApp::ClientAccount'
# ...
end
class ClientAccount < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id, scope: :type
end
module ServiceApp
class ClientAccount < ::ClientAccount
end
end
module SubscriptionApp
class ClientAccount < ::ClientAccount
end
end
module EcomApp
class ClientAccount < ::ClientAccount
end
end
This avoids the very unappealing duplication of having X number of tables in the schema to maintain and the JSONB column still gives you a ton of flexibility. However its in many ways just an upgrade over the EAV pattern.
It also has a lot in common with MTI. In MTI you would use an association to another table which fills the same purpose as the JSON column - to make the relational model more flexible. This can either be polymorphic or you can have X number of foreign keys for each specific type.
One table for each type.
class User < ApplicationRecord
has_one :subscription_account
has_one :service_account
# ...
end
class ClientAccount < ApplicationModel
self.abstract_class = true
belongs_to :user
end
class SubscriptionAccount < ClientAccount
end
class ServiceAccount < ClientAccount
end
# ...
This is the most flexible option but if you want to add a feature you will have to create migrations for each and every table. And this also means that you can't query a single homogenous collection if you want all the types. You have to perform X number of joins.
This is not really that appealing unless the requirements for each type are wildly different.

How to deal with different type of users with rails?

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/

How to setup self-referential polymorphic relationship in 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

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

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