Spree Relation Issue - ruby-on-rails

I'm trying to add product attachment functionality to a Spree store. E.g. a product has many attached documents: brochures, instruction manuals, etc. I can't get the relationship between documents and products to work.
I can use the Paperclip gem for the attachment functionality since Spree already uses it for images.
I have the "document" model: models/spree/document.rb:
class Spree::Document < ActiveRecord::Base
belongs_to :products, class_name: "Spree::Product"
has_attached_file :pdf
end
Then I try to relate the document model to the Spree::Product model in models/spree/product_decorator.rb:
Spree::Product.class_eval do
has_many :documents, dependent: :destroy
end
Then I add migrations:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.timestamps
end
end
end
class AddPdfToDocuments < ActiveRecord::Migration
def self.up
add_attachment :spree_documents, :pdf
end
def self.down
remove_attachment :spree_documents, :pdf
end
end
Now I go into the rails console to see if it worked:
#=> prod = Spree::Product.first
#=> prod.document
#=> PG::UndefinedColumn: ERROR: column spree_documents.product_id does not exist
#=> LINE 1: ..."spree_documents".* FROM "spree_documents" WHERE "spree_doc...
^
#=> : SELECT "spree_documents".* FROM "spree_documents" WHERE "spree_documents"."product_id" = $1
Seems like I'm not defining the relationship between documents and products correctly, but I'm not sure what the issue is.

It looks like you never added a product_id column to your Spree::Documents table. When you define a model belongs_to another model, it tells ActiveRecord that the first one will be a [relation]_id column in its table.
You just need to make sure to add t.references :product in your migration, so it'd look like:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.references :product
t.timestamps
end
end
end

Related

Translations for each model

In my domain, many models have names, descriptions, etc. These properties need translations. I know how to represent this in a database. However I struggle finding a way to represent this with Rails.
|-------translations-table--------|
|translation_id|locale|translation|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|----------------------modelx-table---------------------|
|id|name_translation_id|description_translation_id|price|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|-------modely-table--------|
|id|name_translation_id|date|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
You do not need to create extra models for translations, yo just need to set up locales in .yml format, check this for further instructions
Update
Ok now I understood your point, you want to add translatable fields on your entities/models, so users can manage those translations through a UI right?, well your approach is correct, however there is a gem called Globalize that does the exact same thing but with more toys, and much more standardized as you want.
This is the solution I eventually came up with:
#Models
class Translation
has_many :translation_records
end
class TranslationRecord
(translation_records.find_by :locale => I18n.locale).text
end
class ModelX
belongs_to :name_translation, :class_name => 'Translation'
belongs_to :description_translation, :class_name => 'Translation'
def name
name_translation.current
end
def description
description_translation.current
end
end
#Migrations
class CreateTranslationRecords < ActiveRecord::Migration[5.0]
def change
create_table :translation_records do |t|
t.references :translation
t.string :locale
t.string :text
end
add_index :translation_records, :locale
end
end
class CreateTranslation < ActiveRecord::Migration[5.0]
def change
create_table :translations do |t|
# only id column
end
end
end
class AddTranslationToModelXs < ActiveRecord::Migration[5.0]
def change
add_reference :model_xs, :name_translation
add_reference :model_xs, :description_translation
end
end

Migration for changing belongs_to association

I have a model called categories currently they belong to product but I'd like them to belong to store instead. I have several thousand of these so what I'd like to do is create a migration that adds a store_id to categories and then, gets the associated product.store.id from it's current association and adds that to the store_id. After that I'd like to remove the product association.
Does anybody know how to easily and safely achieve that?
Should you add the association in the wrong direction, you can use change_table to reverse the association:
class CreateFavorites < ActiveRecord::Migration[5.0]
def change
create_table :favorites do |t|
t.belongs_to :article
t.timestamps
end
end
end
class RemoveArticleFromFavorites < ActiveRecord::Migration[5.0]
def change
change_table :favorites do |t|
t.remove_references :article
end
end
end
class AddFavoriteToArticles < ActiveRecord::Migration[5.0]
def change
change_table :article do |t|
t.belongs_to :favorite
end
end
end
First rename column to store_id,
rename_column :categories, :product_id, :store_id
Then change the assosciation.
Now you can either write a rake task to transfer the data or you can manually do it via console.
It's better way to write a rake task.
According to your requirement your rake task can be, get the store from the product and assign to the category according to your requirement.
require 'rake'
namespace :category do
task :product_to_store => :environment do
Category.all.each do |category|
product = Product.find(category.store_id) //you will get product,as now it chnaged to store_id
if product.present?
category.store_id = product.try(:store).try(:id) //assign the product's store_id to the category, use try to reject errored records
category.save
end
end
end
end
Now run, **`rake category:product_to_store`**, thats it, the data gets transfered.
You can just add new migration that will create the new reference with categories as store.
class YourMigrationName < ActiveRecord::Migration
def up
add_reference :categories, :store, index: true
Category.all.each do |category|
category.store_id = category.product_id
category.save
end
remove_column :product_id
end
def down
add_reference :categories, :product, index: true
Category.all.each do |category|
category.product_id = category.store_id
category.save
end
remove_reference :categories, :store, index: true
end
end
May be if you have added the product reference and index then write the same as the store so then it will delete the index as well.
you have data in the column you don't want to lose, then use rename_column

Is it advisable to use :foreign_key in my migrations rather than just adding user_id?

I am using rails 4.2, I just want to know if there would be any difference if I use the :foreign_key keyword in my migrations rather than just adding a user_id column to add relationship to my models ?
YES
The key difference is not on the application layer but on the database layer - foreign keys are used to make the database enforce referential integrity.
Lets look at an example:
class User < ActiveRecord::Base
has_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
If we declare things.user_id without a foreign key:
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :user_id
t.timestamps null: false
end
end
end
ActiveRecord will happily allow us to orphan rows on the things table:
user = User.create(name: 'Max')
thing = user.things.create
user.destroy
thing.user.name # Boom! - 'undefined method :name for NilClass'
While if we had a foreign key the database would not allow us to destroy user since it leaves an orphaned record.
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
user = User.create(name: 'Max')
thing = user.things.create
user.destroy # DB says hell no
While you can simply regulate this with callbacks having the DB enforce referential integrity is usually a good idea.
# using a callback to remove associated records first
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
end

Specific model association case in rails

I have 3 tables: proposals, items/proposals (items is nested inside proposals) and invoices.
I want to create invoices for those items in the proposals that got approved. How would the associations for these look like? Also, how would I set up the invoices form to choose only those items that got approved by the client?
Consider creating two different line items models for Proposal and Invoice.
class Proposal < ActiveRecord::Base
has_many :proposal_line_items
end
class ProposalLineItem < ActiveRecord::Base
belongs_to :proposal
end
class Invoice < ActiveRecord::Base
has_many :invoice_line_items
end
class InvoiceLineItem < ActiveRecord::Base
belongs_to :invoice
end
You can consider having an "approved" attribute in proposal line items. In the invoice form, you can show proposal line items approved by the client.
The suggestion of having separate line items for Proposal and Invoice is based on ERP data modeling principles to maintain the integrity of Invoice.
Update
For example here are the sample migrations for the models suggested
class CreateProposalLineItems < ActiveRecord::Migration
def change
create_table :proposal_line_items do |t|
t.references :proposal, index: true, foreign_key: true
t.string :name
t.integer :approved
t.timestamps null: false
end
end
end
class CreateProposals < ActiveRecord::Migration
def change
create_table :proposals do |t|
t.string :name
t.timestamps null: false
end
end
end
class InvoicesController < ActionController
def new
#approved_items = Proposal.find(params[:proposal_id]).proposal_line_items.where(:approved => 1)
end
end
You can iterate over the #approved_items in your view and display it to users.
V

HABTM and Naming conventions

I've been racking my head around a rails issue for a while and wanted to verify my findings. I was trying to get the Has_and_belongs_to_many relationship working, but couldn't connect my two classes, auctionItem and category. First of all, here was my migration file and the two classes before solving the issue:
Migration file:
class AuctionItemsCategories < ActiveRecord::Migration
def up
create_table 'auction_items_categories', :id=>false do |t|
t.reference :auctionItem_id
t.references :category_id
end
end
def down
drop_table 'auction_items_categories'
end
end
Category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :auction_items
end
auction_item.rb
class AuctionItem < ActiveRecord::Base
has_and_belongs_to_many :categories
end
After creating an instance of AuctionItem, I tried
auction_item = AuctionItem.last
auction_item.categories
...and got the following error:
NoMethodError: undefined method `categories' for #<AuctionItem:0x0000010521b870>
After some research, I found adding the specific class to the has_and_belongs_to_many helped:
Category Model
has_and_belongs_to_many :auction_items , :class_name => 'AuctionItem'
auction_item Model
has_and_belongs_to_many :categories , :class_name => 'Category'
This solved that issue and I was able to access the categories table. I went on to try to append a category to the auction item:
auction_item.categories << category
I then got received the following error:
SELECT "auction_items".* FROM "auction_items" INNER JOIN "auction_items_categories" ON "auction_items"."id" = "auction_items_categories"."auction_item_id" WHERE "auction_items_categories"."category_id" = 2
SQLite3::SQLException: no such column: auction_items_categories.auction_item_id: SELECT "auction_items".* FROM "auction_items" INNER JOIN "auction_items_categories" ON "auction_items"."id" = "auction_items_categories"."auction_item_id" WHERE "auction_items_categories"."category_id" = 2
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: auction_items_categories.auction_item_id: SELECT "auction_items".* FROM "auction_items" INNER JOIN "auction_items_categories" ON "auction_items"."id" = "auction_items_categories"."auction_item_id" WHERE "auction_items_categories"."category_id" = 2
If you notice, the query is trying to get auction_item.id rather than AuctionItem.id. To get the connection to work, I had to change my migration file to the following:
class AuctionItemsCategories < ActiveRecord::Migration
def up
create_table 'auction_items_categories', :id=>false do |t|
t.integer :auction_item_id
t.integer :category_id
end
end
def down
drop_table 'auction_items_categories'
end
end
So long story short/TL DR version: For me, it seems that when naming your class with multiple words and using camel case, rails does not singularize your pluralized class name back to it's original state if it has an underscore. So for example, my class name was AuctionItem which became auction_items for the model. Rather than search for auctionitem.id, the sql call that was looked for was auction_item.id, which is the singularize version of auction_items. Why didn't it search for auctionitem.id? In the future, when I am making association tables with multi word classes, do I use the singular underscore id version of the model name?
Your original migration was incorrect. It should have been as follows:
class AuctionItemsCategories < ActiveRecord::Migration
def up
create_table 'auction_items_categories', :id => false do |t|
t.references :auction_item
t.references :category
end
end
def down
drop_table 'auction_items_categories'
end
end
You should specify the symbolized model name when using references.

Resources