Rails Model Associations when nesting - Confusion - ruby-on-rails

Perhaps I am approaching this all wrong, I have the following models setup:
The User model has many Questions, a Question belongs_to a User
This means I can easily do: u = User.last.questions and see all of the questions associated with the user last added.
Following this, I have a Payment model that belongs_to a Question. A Question has_one payment meaning I can easily do u = User.last.questions.last.payment, getting the payment details from the question last added by the last added user (daft example perhaps!).
Now the bit that is confusing me
I have a model called PaymentOption which is used to populate a drop down list, for different prices, the PaymentOption belongs_to a Payment and a Payment has_one PaymentOption
This is all working correctly, but the only way for me to find out the details of the payment option, such as the amount, would be to do something like this:
payment_amount = PaymentOption.find(User.last.questions.last.payment.payment_option_id).amount
Ideally I would do something like this:
amount = User.last.questions.last.payment.payment_option.amount
By doing it in the way I have, it is as good as doing two seperate queries, one to get the payment option ID and another to find a detail relating to that particular payment option. Surely there is a better way to approach this?
Thanks in advance
EDIT 1
To clarify my answer, I have included the schema for Payments and PaymentOptions
create_table "payment_options", force: :cascade do |t|
t.integer "amount"
t.text "description"
t.string "title"
t.boolean "user_accessible", default: true
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "payments", force: :cascade do |t|
t.string "email"
t.string "token"
t.integer "question_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "payment_option_id"
end
add_index "payments", ["payment_option_id"], name: "index_payments_on_payment_option_id"
add_index "payments", ["question_id"], name: "index_payments_on_question_id"

I think here is you problem:
I have a model called PaymentOption which is used to populate a drop
down list, for different prices, the PaymentOption belongs_to a
Payment and a Payment has_one PaymentOption
You said you've setup Payment has_one PaymentOption and PaymentOption belongs_to Payment. But in the schema described in the end Payment has a payment_option_id.
In this case, Payment belongs_to PaymenteOption and not the opposite. See this example in the Rails Guide. Account has supplier_id so Supplier has one Account and Account belongs_to Supplier.
Your models should be like this:
class Payment < ActiveRecord::Base
belongs_to :payment_option
end
class PaymentOption < ActiveRecord::Base
has_one :payment # Or even has_many :payments
end

Related

How to model and integrate a rails mutliple choice quiz?

I am trying to make a little app where users can sign up, login, and be able to view and interact with questions for educational purposes.
I can visualize everything, but I'm having trouble translating that into actual code.
I know that a question model will have
Question Title - as a string or text
Question Answer 1 - as a string or text
Question Answer 2 - as a string or text
Question Answer 3 - as a string or text
Question Answer 4 - as a string or text
Question CORRECT ANSWER 5 - as a string or text
Naturally, I know the strong_params will have to accept these attributes (parameters?) as well.
How can I make a model where the new-question.html.erb form will pass an array of 5 options, with the ability to mark one as correct? On top of this, I would like to shuffle or randomize the answer choices on each page load.
Any help or guidance would be helpful. Michael Hartl's tutorial is great, but I'm not sure if i'm missing things from it or things aren't clicking.
sample for database schema
create_table "questions", force: :cascade do |t|
t.references "quiz_id"
t.string "question_word"
t.string "option1"
t.string "option2"
t.string "option3"
t.string "option4"
t.integer "answer", default: 0
t.integer "score", default: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "quizs", force: :cascade do |t|
t.string "quiz_name"
t.string "notes"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_quiz", force: :cascade do |t|
t.references "user_id"
t.references "quiz_id"
t.integer "score", default: 0
end
sample model relationship, there are 4 models, User, Quiz, Question, UserQuiz
class Quiz < ActiveRecord::Base
has_many :questions
has_many :user_quizs
has_many :users, :through => :user_quizs
end
class User < ActiveRecord::Base
has_many :user_quizs
has_many :quizs, :through => :user_quizs
end
class Question < ActiveRecord::Base
belongs_to :quiz
end
class UserQuiz < ActiveRecord::Base
belongs_to :user
belongs_to :quiz
end
for user to choose you can use radio_button_tag, here is link to learn
If the number of answers is always 5 or less, there's nothing wrong using a Question model with 5 text fields for the answers. You can also defaulting the first answer is the correct one and in the view showing the question and answers shuffle the answers.
rails g model Question title:text correct_answer:text answer_1:text answer_2:text ...
You're just getting started so don't bother too much with separate model for Question, Answer and nested form. Keep it simple.

Rails 5 - Multiple objects in one form (Non nested)

Using Rails 5 I am trying to create multiple records via one form and am having difficulty finding an answer that is not related to nested forms.
I have a Tutor model which has many availabilities.
class Tutor < ApplicationRecord
has_many :availabilities, dependent: :destroy
end
Availabilities belong to Tutor
class Availability < ApplicationRecord
belongs_to :tutor, optional: true
end
An availability simply has day, from and to attributes.
create_table "availabilities", force: :cascade do |t|
t.integer "day"
t.integer "tutor_id"
t.time "from"
t.time "to"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["tutor_id"], name: "index_availabilities_on_tutor_id",
using: :btree
end
I understand that I can use nested forms to create multiple availabilities when initially creating the tutor.
However what I am trying to do is to create multiple availabilities when the tutor goes to the availability/new page (when the tutor has already created).
Is this possible?

Rails 4 HABTM how to set multiple ids in console?

schema.rb:
ActiveRecord::Schema.define(version: 20150324012404) do
create_table "groups", force: :cascade do |t|
t.string "title"
t.integer "teacher_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "groups_students", id: false, force: :cascade do |t|
t.integer "group_id"
t.integer "student_id"
end
add_index "groups_students", ["group_id"], name: "index_groups_students_on_group_id"
add_index "groups_students", ["student_id"], name: "index_groups_students_on_student_id"
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", default: false
t.string "type"
t.integer "group_id"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
group.rb:
class Group < ActiveRecord::Base
belongs_to :teacher
has_and_belongs_to_many :students
end
student.rb:
class Student < User
has_and_belongs_to_many :groups
end
I could have set a simple belongs_to and a has_many relationship between the student and group models, but I want students to be able to belong to more than one group, so I set up a HABTM association and corresponding join table.
I think I that right?
The question is, how do I, in the console, set a Student to belong to more than one group?
I have setup a User with 'type: Student' and I have two Groups. So...
In the console I do:
student = Student.first
Then, I want to set 'student' to belong to both Groups, but I don't know how to do this.
To set it to belong to one group I can do:
student.update_attributes(group_id: 1)
But how do make it belong to both groups? It would have two group_id's wouldn't it? I don't know how to set this.
If you need to see any of the other files, it's the 'handcode' branch here:
https://github.com/Yorkshireman/sebcoles/tree/handcode
The answers others have already provided are correct. But if you're working with id's you can also do something like this
student = Student.first
student.group_ids = 1,2,3,4
You don't need to set group_id for the User, the association is handled by the join table and the HABTM statement. You should remove group_id from the users table in the schema.
From memory you should be able to do something like this:
student = Student.first
groups = Group.all
student.groups << groups
student.save
See the active record guide on HABTM associations - specfically 4.4.1.3
Instead of habtm, just use the normal through and your life becomes easy. Make sure an id is generated for the association table (remove id:false)
create_table "group_students", force: :cascade do |t|
t.integer :group_id, nil:false
t.integer :student_id, nil:false
end
class Group < ActiveRecord::Base
has_many :group_students, dependent: :destroy, inverse_of :group
has_many :students, through :group_students
end
class Student < ActiveRecord::Base
has_many :group_students, dependent: :destroy, inverse_of :student
has_many :groups, through: :group_students
end
class GroupStudent < ActiveRecord::Base
belongs_to :group,
belongs_to :student
validates_presence_of :group, :student
end
Group.last.students << Student.last
or..
Student.last.groups << Group.last
Student.last.groups = [Group.find(1), Group.find(2)]
etc....
Ok, so it took me 3 days of all kinds of pain to work this out.
There was nothing wrong with my original code, except that I needed to remove the group_id from the user table.
roo's answer was correct, except that using 'group' as a variable name in the console confused Rails. This had led me to believe there was something wrong with my code, but there wasn't. You learn the hard way.
So, Students can be pushed into Groups like this:
To push a student into one group:
student = Student.first
OR
student = Student.find(1)
(or whatever number the id is)
group1 = Group.first
OR
group1 = Group.find(1)
student.groups << group1
To push into multiple groups (which was the original goal of this whole debacle:
student = Student.first
OR
student = Student.find(1)
allclasses = Group.all
student.groups << allclasses
To view your handywork:
student.groups
Works beautifully. The only problem I can see with my code is that it's possible to push the same student into a group twice, resulting in two duplicates of that student in one group. If anyone knows how to prevent this happening, I'm all ears.

How to set up my model-relations properly

I have 3 models: Hacks, Votes, Users.
A user can create many hacks.
Each user should be able to vote on each hack ONCE (Rating of 1-5. Rating should be updateable in case of a missclick or whatever).
I thought about the following relations:
Hack.rb
belongs_to :user
User.rb
has_many :hacks
Votes.rb
belongs_to :user
belongs_to :hack
Is that correct or am I missing something?
I thought about getting all the votes like this later on:
Hack.first.votes
What kind of foreign-keys do I have to set up?
In my schema.rb I already successfully set my users<=>hack relation up, without any foreign keys.
ActiveRecord::Schema.define(version: 20141019161631) do
create_table "hacks", force: true do |t|
t.string "url"
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "email", null: false
t.string "crypted_password", null: false
t.string "salt", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "role"
end
end
Thank you very much in advance!
I think this is what you want to have.
class User.rb
has_many :votes
has_many :hacks, through: :votes
end
class Vote.rb
belongs_to :user
belongs_to :hack
end
class Hack.rb
has_many :votes
end
With this, a hack has many votes through a user.
foreign keys:
votes table: user_id, hack_id
You should be able to do hack.votes
EDIT:
I edited the model to reflect a normal has many through relationship
user -> vote <- hack
a user has many votes
a user has many hacks through votes
a hack has many votes
foreign keys live in the votes table. You can use the following when creating the votes table to indicate the foreign key
t.references user
t.references hack

Rails double-entry method

1-account.rb
class Account < ActiveRecord::Base
belongs_to :transaction
end
class Supplier < Account
end
class Expense < Account
end
2-transaction.rb
class Transaction < ActiveRecord::Base
has_many :accounts
accepts_nested_attributes_for :accounts
end
3-migration schema
create_table "accounts", :force => true do |t|
t.string "name"
t.decimal "debit"
t.decimal "credit"
t.decimal "balance"
t.string "type"
t.integer "transaction_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "transactions", :force => true do |t|
t.string "name"
t.decimal "amount"
t.date "date"
t.string "document"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Question1:
What's the best method to reach supplier and expense in the view (see the picture below)?
Question2:
How can I implement a method that automatically record the transaction amount in expense_debit and supplier_credit, and vice versa? (View screenshot)
I'd suggest an alternative setup. For one thing, your transaction does not record which is the "from" account and which is the "to" account. That's kind of important to know for later.
I recognise that there may be multiple accounts from and to... but debit vs credit should be recorded on a per-transaction basis, not in just one big lump on the Account, otherwise it'll be difficult later to, say, list the change in an account's value between date A and date B (important for tax-returns or quarterly sales reports or whatever).
i don't know about naming, but perhaps a transaction has_many account-transfers and a transfer is "transaction_id, account_id, amount, direction" (where direction is debit/credit).

Resources