Rails + simple role system through associative table - ruby-on-rails

So I have the Ninja model which has many Hovercrafts through ninja_hovercrafts (which stores the ninja_id and the hovercraft_id).
It is of my understanding that this kind of arrangement should be set in a way that the associative table stores only enough information to bind two different classes.
But I'd like to use the associative table to work as a very streamlined authorization hub on my application. So i'd also like this table to inform my system if this binding makes the ninja the pilot or co-pilot of a given hovercraft, through a "role" field in the table.
My questions are:
Is this ugly?
Is this normal?
Are there methods built into rails that would help me to automagically create Ninjas and Hovercrafts associations WITH the role? For exemple, could I have a nested form to create both ninjas and hcs in a way that the role field in ninjas_hovercrafts would be also filled?
If managing my application roles this way isn't a good idea, whats the non-resource heavy alternative (my app is being designed trying to avoid scalability problems such as excessive joins, includes, etc)
thank you

This might not answer you question completely, but if you are only going to have two roles for hovercrafts I would instead set up the associations like this
class Hovercraft < ActiveRecord::Base
belongs_to :pilot, :class_name => 'Ninja', :foreign_key => 'pilot_id'
belongs_to :copilot, :class_name => 'Ninja', :foreign_key => 'copilot_id'
end
class Ninja < ActiveRecord::Base
has_many :pilotings, :class_name => 'Hovercraft', :foreign_key => 'pilot_id'
has_many :copilotings, :class_name => 'Hovercraft', :foreign_key => 'copilot_id'
end
Now if you have more roles than that, or if you need more flexibility you can use a third model to link them together.
class Hovercraft < ActiveRecord::Base
has_many :hovercraft_roles
has_many :ninjas, :through => :hovercraft_roles
end
class HovercraftRole < ActiveRecord::Base
belongs_to :hovercraft
belongs_to :ninja
end
class Ninja < ActiveRecord::Base
has_many :hovercraft_roles
has_many :hovercrafts, :through => :hovercraft_roles
end
With a role attribute in HovercraftRole model to indicated if it is a 'pilot' or 'copilot'.

Related

Extending gem/engine models to include acts_as_tenant from main app

There are a few post on this subject but the light has not turned on yet.
I'm trying to extend a rails gem/engine Plutus to use acts_as_tenant
Plutus provides a double entry accounting system for an application. One of the limitations is that the design allows for only one customer or one set of books. What I am trying to do is add multi-tenancy using acts_as_tenant with as little as possible modifications to the Plutus engine. The goal is not to significantly alter Plutus with a different fork, but to add a few optional methods or attributes to Plutus that are only used if you want multiple accounts.
I have it semi-working, but need help in finding where to put stuff and help in clear up what is not working. The condensed Plutus models are:
class Account < ActiveRecord::Base
has_many :credit_amounts, :extend => AmountsExtension
has_many :debit_amounts, :extend => AmountsExtension
has_many :credit_transactions, :through => :credit_amounts, :source => :transaction
has_many :debit_transactions, :through => :debit_amounts, :source => :transaction
end
class Amount < ActiveRecord::Base
belongs_to :transaction
belongs_to :account
end
class Transaction < ActiveRecord::Base
belongs_to :commercial_document, :polymorphic => true
has_many :credit_amounts, :extend => AmountsExtension
has_many :debit_amounts, :extend => AmountsExtension
has_many :credit_accounts, :through => :credit_amounts, :source => :account
has_many :debit_accounts, :through => :debit_amounts, :source => :account
end
Then sti classes on Account: Asset, Equity, Expense, Liability, Revenue and sti classes on Amount: DebitAmount, CreditAmount. This is a little beyond my rails knowledge but this may be on of the most compact double entry schemes I've ever seen (I'm not an accountant, but I have had to add accounting features on apps in the past).
Semi-working means that the only thing I've modified in Plutus is adding a tenant_id to the three models and getting acts_as_tenant to extend two of the three models. From the console on the main application I've found that:
Plutus::Account.acts_as_tenant(:tenant)
Plutus::Amount.acts_as_tenant(:tenant)
Plutus::Transaction.acts_as_tenant(:tenant)
works for Account and Transaction, but errors on Amount with uninitialized constant Transaction, and I'm not sure why. Any ideas?
I've read the rails guide on engines and extending with decorators or concerns, but have not figured out how to send acts_as_tenant(:tenant) to the model using those approaches. Where would I put those three lines of code in the main application (providing I figure out how to get Amount to work!)?
Is there a better approach?
I still have a few class methods that I will have to extend or modify, but no use trying that until I get over this first hurdle.
The problem was that Plutus is a name-spaced engine, and while it works fine within the engine, calling it from outside the engine (main app) could raise conflicts.
To fix it, a class_name option was added to the associations.
module Plutus
class Amount < ActiveRecord::Base
belongs_to :transaction, class_name:"Plutus::Transaction"
belongs_to :account, class_name:"Plutus::Account"
validates_presence_of :type, :amount, :transaction, :account
end
end
Still never figured out the best place to stick the ActsAsTenant calls. I stuck them in the Concerns directory and they didn't get called. Ended up putting them in my Tenant model and all is fine.

Saving a rails has_many association using multiple keys

I have the following models:
class Product < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :recommendation_sets, :through => :product_recommendation_sets
end
class RecommendationSet < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :products, :through => :product_recommendation_sets
has_many :recommendations
end
class Recommendation < ActiveRecord::Base
belongs_to :recommendation_set
end
And am adding recommendations recommendations_set like so:
p = Product.find_by_wmt_id(product) || Product.create( ItemData.get_product_data(product) )
recommendation = find_by_rec_id(rec_id) || create( ItemData.get_product_data(rec_id) )
rec_set = RecommendationSet.find_or_create_by_rating_set_id_and_model_version_and_product_id(rating_set.id, model_version, p.id)
sec_set.update_attributes(
:rating_set_id => rating_set.id,
:product_id => p.id,
:model_version => model_version,
:notes => note
)
sec_set.recommendations << recommendation
sec_set.save
prs = ProductRecommendationSet.find_or_create_by_recommendation_set_id_and_rating_set_id_and_product_id(rec_set .id, rating_set.id, p.id,)
prs.update_attributes(
:recommendation_set_id => rec_set.id,
:rating_set_id => rating_set.id,
:product_id => p.id
)
This works as expected, however my problem is that I have multiple recommendation_sets which belong to multiple products, and each of the recommendation_sets may have the same recommendation. By saving each recommendation to a recommendation_set as I am currently doing, if two recommendation_sets have the same recommendation, only one of the sets will add that recommendation. Is there anyway of saving each recommendation to multiple recommendation_sets using a secondary id, such as save by recommendation_id_and_product_id, or would I need to change this releationship to a has_many :through?
Based on your clarification, I think you basically have a many-to-many relationship between RecommendationSet and Recommendation. Presently, you have a one-to-many.
There are a couple of options:
Use the has_and_belongs_to_many method in both models to describe the relationship;
Manually create a "join" model and then give both RecommendationSet and Recommendation a has_many to this join model (with two corresponding belongs_to lines in the join model pointing to the other two models);
A has_many ... :through style, like you mentioned
Note that the first two options require you to have a join table.
If you require additional information on the join table/model, I tend to go with the 2nd option. Otherwise, either the first or third are perfectly valid.
Ryan Bates of RailsCasts made an episode about this here: http://railscasts.com/episodes/47-two-many-to-many
And some more information from the Rails documentation: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Many-to-many
In short, if you don't need extra info on the join, I think your idea of the has_many ... :through is perfectly fine.
Let me know whether that helps

Has_many: through in Rails. Can I have two foreign keys?

I am a rails newbie and I am trying to create a database schema that looks like the following:
There are many matches. Each match has 2 teams.
A team has many matches.
The team model and match model are joined together through a competition table.
I have that competition model with a match_id and a team1_id and a team2_id.
But I don't know how to make this work or if it's even the best way to go about it. I don't know how to make certain teams team1 and others team2.... two foreign keys? Is that possible?
The match table also needs to hold additional data like team1_points and team2_points, winner and loser, etc.
You can have as many foreign keys as you want in a table. I wrote an application that involved scheduling teams playing in games.
The way that I handled this in the Game class with the following:
class Game < ActiveRecord::Base
belongs_to :home_team, :class_name => 'Team', :foreign_key => 'team1_id'
belongs_to :visitor_team, :class_name => 'Team', :foreign_key => 'team2_id'
You can add appropriate fields for team1_points, team2_points, etc. You'll need to set up your Team model with something like:
class Team < ActiveRecord::Base
has_many :home_games, :class_name => 'Game', :foreign_key => 'team1_id'
has_many :visitor_games, :class_name => 'Game', :foreign_key => 'team2_id'
def games
home_games + visitor_games
end
#important other logic missing
end
Note that some of my naming conventions were the result of having to work with a legacy database.
I faced a similar problem, and extending the previous answer, what I did was:
class Game < ActiveRecord::Base
def self.played_by(team)
where('team1_id = ? OR team2_id = ?', team.id, team.id)
end
end
class Team < ActiveRecord::Base
def games
#games ||= Game.played_by(self)
end
end
This way, Team#games returns an ActiveRecord::Relation instead of an Array, so you can keep chaining other scopes.

has_one :through => multiple

Both Attendment & Vouching:
belongs_to :event
belongs_to :account
Therefore: 1 to 1 relationship between attendments and vouchings.
Is there a way to do this without my thinking too much?
# attendment
has_one :vouching :through => [:event, :account]
Note: I don't mind thinking too much, actually.
Yeah i don't think you can use a has_one for this. Assuming I'm reading this correctly, you have two models:
Attendment
Vouching
They both store an event_id and account_id. You want to know from the attendment model, what vouching shares the same event and account as the attendment. I think the easiest solution for this is to write a method inside your attendment.rb file.
class Attendment < ActiveRecord::Base
# belong to statements go here
def voucher
Voucher.where(:event_id => self.event_id, :account_id => self.account_id).first
end
end

How do I do multiple has_and_belongs_to_many associations between the same two classes?

I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end

Resources