Trouble changing to has_many through from has_many and belongs_to - ruby-on-rails

I've done about a billion searches and tried a number of things here but I'm still getting errors. I've recently changed to a has many through via a model called joinable(maybe thats the problem) and I can't seem to get things working straight. Part of me thinks it something small as I get the idea of it all but I'm not sure I've done it correctly. I'm also using devise.
Here is what I think are all the relevant portions
User
class User < ActiveRecord::Base
acts_as_voter
has_many :joinables
has_many :pits, through: :joinables
has_many :comments
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
class Pit < ActiveRecord::Base
validates :topic, :author, :summary, presence: true
acts_as_taggable
acts_as_votable
has_many :comments
has_many :joinables
has_many :users, through: :joinables
mount_uploader :image, ImageUploader
I created a separate table called "joinable" and now I'm stuck figuring out how to populate it. I can create a user, but can't create a pit. Do I need to revamp my controllers or is their something small I may be missing? I get the idea but some of the little details are fuzzy based on all that I've read so far. I even tried a HABTM with a join table called Pit_Users.
I'm currently getting "Could not find table 'joinables"
coming from here in my controller
def create
#pit = current_user.pits.create(pit_params)
recent migration
class Joinable < ActiveRecord::Migration
create_table :joinable do |t|
t.integer :pit_id, :user_id
t.timestamps
end
end
I've tried a number of combinations all with similar errors. Many of the tutorials/guides are good with the basics but then seem to be leaving out a few details. That or I'm just missing them. Anyways. Would love it if someone more knowledgeable could point out what are probably obvious mistakes. Thanks.

In the migration file, it should be:
class Joinables < ActiveRecord::Migration
create_table :joinables do |t|
t.integer :pit_id
t.integer :user_id
end
end
And in the app/models/joinable.rb, there should be:
class Joinable < ActiveRecord::Base
belongs_to :user
belongs_to :pit
end
You can verify if it is working at the Rails console. Try this to get a Pit record with the association:
user_1 = User.create( ... )
pit_1 = user_1.pits.create!( ... )
pit_1.users.first # should give you user as user_1

Solution is to run rails generator for model
Run from console
rails generate model Joinable pit:references user:references
And delete your migration file for
class Joinable < ActiveRecord::Migration
create_table :joinable do |t|
t.integer :pit_id, :user_id
t.timestamps
end
end
After running rails generator you will get model named Joinable that is required for relations when using through and it will create appropriate migration for you.

Related

Defining "has_many through: assocation" with scoped conditionals in the through model

I have a schema that looks something like this:
create_table "customers" do |t|
t.integer "customer_number"
end
create_table "past_payments" do |t|
t.integer "customer_number"
t.datetime "transaction_date"
t.integer "arbitrary_sequence_number"
end
create_table "payment_details" do |t|
t.datetime "transaction_date"
t.integer "arbitrary_sequence_number"
end
TL;DR from the schema - A Customer is associated with a past_payment through a primary/foreign key. And a PastPayment is associated with a single PaymentDetail when their transaction_date AND arbitrary_sequence_number are equal. Payments and Details have no formal primary/foreign key relationship.
That gives me the following ActiveRecord models:
class Customer < ActiveRecord::Base
has_many :past_payments, foreign_key: :customer_number, primary_key: :customer_number
has_many :payment_details, through: :past_payments # unfortunately, broken 😢
end
class PastPayment < ActiveRecord::Base
has_one :payment_detail, ->(past_payment) {
where(arbitrary_sequence_number: past_payment.arbitrary_sequence_number)
}, foreign_key: :transaction_date, primary_key: :transaction_date
end
Since a Customer has_many :past_payments and a PastPayment has_one :payment_detail, I would think there's an association that can be defined such that a Customer has_many :payment_details, through: :past_payments, but I can't get that to work because of the scope defined on the has_one :payment_detail association.
Specifically, calling Customer.payment_details raises the NoMethodError: undefined method 'arbitrary_sequence_number' for #<Customer:0x2i8asdf3>. So it would seem the Customer is getting passed to my scope as opposed to the PastPayment.
Is it possible to define the has_many :payment_details association on the Customer? Am I doing something wrong?
To be clear, I'd like to be able to say Customer.where(some_conditions).includes(:payment_details) and execute just the two queries so if there's a way to accomplish that without associations, I'm open to it.
Note: I can't change this database. It's a database some other application writes to, and I need to read from it.
Unrelated to my question, here's the workaround I'm currently working with. If there is no way to properly use associations, I'd be happy to have this solution critiqued:
class Customer < ActiveRecord::Base
attr_writer :payment_details
def payment_details
#payment_details ||= Array(self).with_payment_details.payment_details
end
module InjectingPaymentData
def with_payment_details
results = self.to_a
return self unless results.first.is_a?(Customer)
user_ids = results.collect(&:id)
# i've omitted the details of the query, but the idea is at the end of it
# we have a hash with the customer_number as a key pointing to an array
# of PaymentDetail objects
payment_details = PaymentDetails.joins().where().group_by(&:customer_number)
results.each do |customer|
customer.payment_details = Array(payment_details[customer.customer_number])
end
end
end
end
ActiveRecord::Relation.send(:include, Customer::InjectingPaymentData)
Array.send(:include, Customer::InjectingPaymentData)
And with that I can do things like the following with minimal querying:
#customers = Customer.where(id: 0..1000).with_payment_details
#customers.each { |c| do_something_with_those_payment_details }
Problems with that approach?
You can leverage this gem to better handle "composite primary keys" in ActiveRecord: https://github.com/composite-primary-keys/composite_primary_keys
class Customer < ActiveRecord::Base
has_many :past_payments, foreign_key: :customer_number, primary_key: :customer_number
has_many :payment_details, through: :past_payments # this works now
end
class PastPayment < ActiveRecord::Base
has_one :payment_detail, foreign_key: [:transaction_date, :arbitrary_sequence_number],
primary_key: [:transaction_date, :arbitrary_sequence_number]
end

activerecord relation model name displaying twice

Good afternoon,
First post here. I have done a bit of research into this error and not finding anything helpful to me, and because stackoverflow community is my number 1 place to find a answer. I thought why not ask here.
I have created a new rails app, and have created a relation between the order and order_item model.
app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :customer, foreign_key: 'customer_id'
belongs_to :shop
has_many :order_items, dependent: :destroy
end
app/models/order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :order
end
The relationship between customer and order is working because I have created a record for both.
eg
Customer.create(:customer_attributes).orders.create
but when I try to access or create a order item
Customer.first.orders.first.order_items.first || Customer.first.orders.first.order_items.create
I get the following error
ActiveRecord::StatementInvalid: Could not find table 'order_order_items'
my question is, why is it looking for the table name with the prefix twice?
below is what my migration files look like for both models
db/migrate/orders
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.integer :shop_id
t.integer :customer_id
t.string :status
t.string :additional_info
t.timestamps null: false
end
end
end
db/migrate/order_items
class CreateOrderItems < ActiveRecord::Migration
def change
create_table :order_items do |t|
t.integer :order_id
t.integer :menu_item_id
t.timestamps null: false
end
end
end
Any help will be greatly appreciated. New to ruby and rails, so please excuse my lack of knowledge.
Cheers
EDITED
Hey guys! Thank for fast replies, I figured out what the problem was. Earlier in the project I had tried to implement namespacing into the models and this is what was causing the trouble. Once i moved all models directly under the app/models dir and restarted the console all worked 100%. So im guessing I dont understand enough yet of how rails handles the namespacing, but im going to try the namespacing at later stage. For now I just need a quick prototype up an running to demo to a few clients.
Once again thanks for the fast replies.

Seeding an ActiveRecord Database with objects from another table

I'm new to Rails, I'm making my first app and I have (another) question:
I'm adding a "decks of cards" feature to my flashcard app, but having trouble with the modeling to generate a "deck" that is populated by card objects from my card table.
Here are my associations:
User has_many :decks
Deck belongs_to :user
Deck has_many :cards
Card belongs_to :deck
class Card < ActiveRecord::Base
belongs_to :deck
validates :front, presence: true
validates :back, presence: true
validates :deck_id, presence: true
end
And here's my decks migration/table:
class Decks < ActiveRecord::Migration
def change
create_table :decks do |t|
t.string :name, null: false
t.string :card
t.integer :user_id, null: false
end
end
end
My issue is that I would like for the "card" column in the decks table to consist of Card objects, so that I can access/manipulate their methods, but I'm not sure how exactly to do this. I tried populating the table with t.string of ":card" in the hopes that this would work but it only comes up blank. I am wondering if this is even possible or advisable or is there a better way?
If anyone can point me to resources/offer advice on this, thank you. I checked the docs/SO and can't seem to find anything.
What you're describing can be easily accomplished.
Provided that User, Deck and Card are ActiveRecord models, you can associate them by setting foreign keys to connect the tables. A foreign key is an integer column that contains the id of the associated model (its table's primary key)
The Rails convention is to use belongs_to and has_many to declare "one to many" associations (docs). These methods will add to your model objects the required methods to represent and interact with the association.
On the DB schema side of things, you'll need to set the foreign keys on the models' tables that declare the belongs_to associations.
So, if you have these models:
class User < ActiveRecord::Base
has_many :decks
end
class Deck < ActiveRecord::Base
belongs_to :user
has_many :cards
end
class Card < ActiveRecord::Base
belongs_to :deck
end
You'll need these migrations:
class CreateDecks < ActiveRecord::Migration
def change
create_table :decks do |t|
# your other columns...
t.integer :user_id
end
end
end
class CreateCards < ActiveRecord::Migration
def change
create_table :cards do |t|
# your other columns...
t.integer :deck_id
end
end
end
With a has_many relation you don't store the foreign key on the owning table. Instead you have a deck_id column in the cards table.
An example of using an association:
# Load a deck and include the cards in the query
#deck = Deck.joins(:cards).last
#deck.cards.each do |card|
puts card.front
end
# create a new card
#deck.cards.new(front: 'foo', back: 'bar')
#deck.save # will save the card as well.
http://guides.rubyonrails.org/association_basics.html
You don't want the card column in your decks table. The has_many :cards and belongs_to :deck lines in your models provide that functionality for you to be able to do things like #deck.cards.
You just have to be sure to assign a deck_id when you create a new Card object.
You should read through the Rails Guides on associations, and also on database seeding.

Rails - acts_as_list with multiple Models

I've managed to use act_as_list with my Models (it was quite easy) one by one, but now i have a problem.
In my app there are 3 models: Facility, Service and Activity. I need to use acts_as_list on their union... is it possible to do that?
Hope my question is clear
You should use a fourth model with a polymorphic association, then put the list on that.
First, read up on polymorphic associations to understand this: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Now you'll want to have a class that looks like this:
class Position < ActiveRecord::Base
belongs_to :positionable, polymorphic: true
end
And a migration that looks like this:
class CreatePositions < ActiveRecord::Migration
def change
create_table :position do |t|
t.integer :positionable_id
t.string :positionable_type
t.timestamps
end
end
end
Then on each of the other models add this:
class Facility < ActiveRecord::Base
has_one :position, as: :positionable
# ...
end

Scaffolding ActiveRecord: two columns of the same data type

Another basic Rails question:
I have a database table that needs to contain references to exactly two different records of a specific data type.
Hypothetical example: I'm making a video game database. I have a table for "Companies." I want to have exactly one developer and exactly one publisher for each "Videogame" entry.
I know that if I want to have one company, I can just do something like:
script/generate Videogame company:references
But I need to have both companies. I'd rather not use a join table, as there can only be exactly two of the given data type, and I need them to be distinct.
It seems like the answer should be pretty obvious, but I can't find it anywhere on the Internet.
Just to tidy things up a bit, in your migration you can now also do:
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
And since you're calling the keys developer_id and publisher_id, the model should probably be:
belongs_to :developer, :class_name => "Company"
belongs_to :publisher, :class_name => "Company"
It's not a major problem, but I find that as the number of associations with extra arguments get added, the less clear things become, so it's best to stick to the defaults whenever possible.
I have no idea how to do this with script/generate.
The underlying idea is easier to show without using script/generate anyway. You want two fields in your videogames table/model that hold the foreign keys to the companies table/model.
I'll show you what I think the code would look like, but I haven't tested it, so I could be wrong.
Your migration file has:
create_table :videogames do |t|
# all your other fields
t.int :developer_id
t.int :publisher_id
end
Then in your model:
belongs_to :developer, class_name: "Company", foreign_key: "developer_id"
belongs_to :publisher, class_name: "Company", foreign_key: "publisher_id"
You also mention wanting the two companies to be distinct, which you could handle in a validation in the model that checks that developer_id != publisher_id.
If there are any methods or validation you want specific to a certain company type, you could sub class the company model. This employs a technique called single table inheritance. For more information check out this article: http://wiki.rubyonrails.org/rails/pages/singletableinheritance
You would then have:
#db/migrate/###_create_companies
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :type # required so rails know what type of company a record is
t.timestamps
end
end
def self.down
drop_table :companies
end
end
#db/migrate/###_create_videogames
class CreateVideogames < ActiveRecord::Migration
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
def self.down
drop_table :videogames
end
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :videogames
common validations and methods
end
#app/models/developer.rb
class Developer < Company
developer specific code
end
#app/models/publisher.rb
class Publisher < Company
publisher specific code
end
#app/models/videogame.rb
class Videogame < ActiveRecord::Base
belongs_to :developer, :publisher
end
As a result, you would have Company, Developer and Publisher models to use.
Company.find(:all)
Developer.find(:all)
Publisher.find(:all)

Resources