Confusion with has_many :through - ruby-on-rails

I am completely confused with implementing has_many :through for the dataset I am using. So I have two tables - scheme_master and scheme_detail
scheme_master has these fields - id, scheme_detail_id, primary_scheme_id
scheme_detail has one relevant field - id
Every scheme in scheme_master has a primary scheme which is self referential to the scheme_master table. For instance, scheme 1 is the primary_scheme of schemes 1,2,3.
Relevant codes are as below
scheme_master.rb
class SchemeMaster < ActiveRecord::Base
has_one :scheme_detail
has_many :child_schemes, class_name: "SchemeMaster",
foreign_key: :primary_scheme_id, primary_key: :id
end
scheme_detail.rb
class SchemeDetail < ActiveRecord::Base
belongs_to :scheme_master
end
My question is how do I access Scheme Details of all my child schemes?
Currently,
SchemeMaster.find(1).child_schemes
gives me all the child schemes - 1,2,3, but I want an association which would refer to scheme_detail of the child_schemes. Thank you.

Firstly, scheme_masters table has scheme_detail_id, so you need to change this association
class SchemeMaster < ActiveRecord::Base
belongs_to :scheme_detail
end
class SchemeDetail < ActiveRecord::Base
has_one :scheme_master
end
Now, to fetch all scheme_detail of the child_schemes, do this
scheme_master = SchemeMaster.includes(child_schemes: :scheme_detail).where(id: 1).first
To fetch scheme_detail of child_schemes, you can do, scheme_master.child_schemes.first.scheme_detail.
Hope that helps!

Related

Rails 4 Associations: Help setting up database

I need some assistance with my Rails 4 associations. I have the following 4 models:
class User < ActiveRecord::Base
has_many :check_ins
has_many :weigh_ins, :through => :check_ins
has_many :repositionings, :through => :check_ins
end
class CheckIn < ActiveRecord::Base
belongs_to :user
has_one :weigh_in
has_one :repositioning
end
class Repositioning < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
class WeighIn < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
Question: If I am setup this way, how would I input repositionings and weigh_ins separately, but still have them linked through a single check in?
You would have to retain one of the other association's ID in order to make it work.
For example, let's say:
You have created a CheckIn.
You now add a Repositioning to that check in.
Store the ID of the repositioning object
When adding your WeighIn object, you would simply reference the correct CheckIn record: correct_checkin_record = CheckIn.where(repositioning: the_repositioning_id)
You can then add the WeighIn object to that particular record.
An alternative (and simpler) method would be to access the CheckIn directly through the User: correct_checkin_record = #user.checkin -- This would pull in the correct CheckIn every time.
I've included both options to help visualize exactly what is going on in the relation.
Do you want to have users input weigh_ins and repositionings on different pages?
Having weigh_ins and repositionings inputted separately but still be part of a single checkin is fine with that setup. Its just matter of getting the same check_in object and make the associations to that object, which can be done through the controller by passing in check_in ID params and do CheckIn.find(params[:id])

Rails - a model belongs_to other models single reference

Simple association question.
I have a Location model with the following fields:
- Name
- Address
- Lat
- Long
This model should have many "locationables".
Ok. I also have the following models:
Lodging
has_one location (location id)
Transportation
has_one start_location, class_name: location
has_one end_location, class_name: location
So, in this situation I should have a "belongs_to" in my Location model? Or I don't need anything and just put "belongs_to" in each other model?. That seems wrong, right? It seems so simple yet my head is not solving it.
To create proper cross reference you need to have either ::belongs_to or ::has_and_belongs_to_many associations in a class to initiate a direct reference. The first creates class_id field (and attribute readers) in the class, the second additional cross reference join table named like "class_klasses". If you have no eithers, you can properly setup the reference by tying the classes, because ::has_one, and ::has_many just use back references, not direct ones. So you will need something like follows:
class Locationable < ...
belongs_to :location
end
class Lodging < ...
belongs_to :location
end
class Transportation < ...
belongs_to start_location, class_name: :Location
belongs_to end_location, class_name: :Location
end
or course to keep the db more clean you can use through key for (for example) Lodging class, but only then if you already have an assotiation in it:
class Locationable < ...
belongs_to :location
end
class Lodging < ...
belongs_to :locationable
has_one :location, through: :locationable
end

Rails: Making sure two tables are "sync'd"

Relationships:
Team has many Rosters
Team has many Announcements
Announcement has one AnnouncementRoster
When creating an Announcement, the user must assign a Roster to it (whose data will be used to create its new AnnouncementRoster).
My models:
class User < ActiveRecord::Base
has_many :roster_players
has_many :rosters, -> { uniq } , :through => :roster_players
has_many :announcement_rosters, -> { uniq } , :through => :announcement_players
end
class Roster < ActiveRecord::Base
has_many :roster_players
has_many :users, -> { uniq }, through: :roster_players
end
class RosterPlayer < ActiveRecord::Base
belongs_to :roster
belongs_to :user
validates :user_id, :uniqueness => { :scope => :roster_id }
end
class Announcement < ActiveRecord::Base
has_one :announcement_roster
end
class AnnouncementRoster < ActiveRecord::Base
has_many :announcement_players
has_many :users, -> { uniq }, through: :announcement_players
end
class AnnouncementPlayer < ActiveRecord::Base
belongs_to :announcement
belongs_to :user
end
I believe I need different relationship models (RosterPlayer, AnnouncementPlayer, TaskPlayer, etc.) because different relationships require different attributes. For example: an AnnouncementPlayer has a read? column, TaskPlayer has a progress column, and RosterPlayer has a captain? column.
Question:
What is the best way to make sure that the AnnouncementRoster is "sync'd" with the Roster it was copied from? In other words, if the copied Roster's data is changed (a user is added or removed from it) than the corresponding AnnouncementRoster should be updated accordingly.
The strategies you want to decide between, are:
use database-level idioms to cascade those updates (invisibly to rails)
Use after_save and after_update, having Rails push the changes
Create non-automatic methods (rake tasks) in Rails to push the updates on a cron
Push the changes with SQL scripts living in bash and cron-controlled (or similarly, have the changes sent by pigeon)
Which of these you choose is really dependent on your environment, application metrics, resiliency, etc.
Sorry this is an overview answer. Someone else can probably talk through the pro-con sides of this and help you make that decision based on yet-unstated info.

ActiveRecord query without *_id

I have 3 simple models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Product < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
I can do a_subscription.product = a_product and AR knows I mean product_id and everything works fine.
But If i do:
Subscription.where :product => a_product
It throws an error at me Unknown column 'subscriptions.product' - It knows in the first case that I mean product_id but it doesn't in the latter. I am just wondering if this is how it is suppose to be or am I missing something? I can get it to work by saying
Subscription.where :product_id => a_product
by do I have to specify _id?
Yes, right now you can't pass association to the where method. But you'll be able to do it in Rails 4. Here is a commit with this feature.
I don't think there's an elegant way around that (as of now, see #nash 's answer). However, if you have an instance of a_product and it has has_many on subscriptions, why not just turn it around and say:
subscriptions = a_product.subscriptions

Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations

I have 3 models: Question, Option, Rule
Question has_many options;
Option needs a foreign key for question_id
Rule table consists of 3 foreign_keys:
2 columns/references to question_ids -> foreign keys named as 'assumption_question_id' and 'consequent_question_id'
1 column/reference to option_id -> foreign key named as option_id or condition_id
Associations for Rule:
Question has_many rules; and
Option has_one rule
I want to understand how to write up migrations for this, and how that associates to the 'has_many'/'belongs_to' statements I write up in my model, and the ':foreign_key' option I can include in my model.
I had this for my Option migration, but I'm not sure how the "add_index" statement works in terms of foreign keys, and how I can use it for my Rule migration: (my Question and Options models have appropriate has_many and belongs_to statements - and work fine)
class CreateOptions < ActiveRecord::Migration
def change
create_table :options do |t|
t.integer :question_id
t.string :name
t.integer :order
t.timestamps
end
add_index :options, :question_id
end
end
Thank you for the help!
Note: I have found this way to solve the problem.Kindness from China.
If you have RailsAdmin with you,you may notice that you can see all rules of one question as long as one field of both question fields(assumption_question_id,consequent_question_id) equals to id of the question.
I have done detailed test on this and found out that Rails always generates a condition "question_id = [current_id]" which make to_sql outputs
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170
And the reason that the following model
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
makes Question.take.rules.to_sql be like this
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170 AND (assumption_question_id = 170 OR consequent_question_id = 170)
Is that we have not yet get ride of the annoy question_id so no matter how we describe or condition properly, our condition follows that "AND".
Then,we need to get ride of it.How?
Click here and you will know how,Find sector 8.1,and you can see
Article.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "articles".* FROM "articles" WHERE trashed = 0
Then lets do it:
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { unscope(where: :question_id).where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
All done.
Finally
This is my first answer here at stackoverflow,and this method is never found anywhere else.
Thanks for reading.
add_index adds an index to column specified, nothing more.
Rails does not provide native support in migrations for managing foreign keys. Such functionality is included in gems like foreigner. Read the documentation that gem to learn how it's used.
As for the associations, just add the columns you mentioned in your Question to each table (the migration you provided looks fine; maybe it's missing a :rule_id?)
Then specify the associations in your models. To get you started
class Question < ActiveRecord::Base
has_many :options
has_many :assumption_rules, class_name: "Rule"
has_many :consequent_rules, class_name: "Rule"
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
Note This is just a (untested) start; options may be missing.
I strongly recommend you read
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://guides.rubyonrails.org/association_basics.html
Edit: To answer the question in your comment
class Option < ActiveRecord::Base
belongs_to :question
# ...
The belongs_to tells rails that the question_id column in your options table stores an id value for a record in your questions table. Rails guesses the name of the column is question_id based on the :question symbol. You could instruct rails to look at a different column in the options table by specifying an option like foreign_key: :question_reference_identifier if that was the name of the column. (Note your Rule class in my code above uses the foreign_key option in this way).
Your migrations are nothing more than instructions which Rails will read and perform commands on your database based from. Your models' associations (has_many, belongs_to, etc...) inform Rails as to how you would like Active Record to work with your data, providing you with a clear and simple way to interact with your data. Models and migrations never interact with one another; they both independently interact with your database.
You can set a foreign key in your model like this:
class Leaf < ActiveRecord::Base
belongs_to :tree, :foreign_key => "leaf_code"
end
You do not need to specify this in a migration, rails will pull the foreign key from the model class definition.

Resources