Paperclip attachment shared by 2 models - ruby-on-rails

I am trying to roll out my own e-commerce solution. Extending the depot application explained in the Pragmatic Web Development with Rails book.
I am currently trying to figure out attachments.
Essentially I want Product and Product_Variants to use Product_Shots for attached photos.
This could result in the product_shots table with empty values for product_variants, because not all products have prodcut_variants. Is there a better approach to implement this?
Product model:
class Product < ActiveRecord::Base
validates :title, :description, :price, :presence=>true
validates :title, :uniqueness => true
validates :price, :numericality =>{:greater_than_or_equal_to => 0.01}
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
has_and_belongs_to_many :product_categories
has_many :product_variants
has_many :product_shots, :dependent => :destroy
accepts_nested_attributes_for :product_shots, :allow_destroy => true,
:reject_if => proc { |attributes| attributes['shot'].blank?
}
private
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, "Line items present")
end
end
end
Product Variant model
class ProductVariant < ActiveRecord::Base
belongs_to :product
belongs_to :product_categories
has_many :variant_attributes
has_many :product_shots # can I do this?
end
Product Shots model (handled by Paperclip)
class ProductShot < ActiveRecord::Base
belongs_to :product, :dependent =>:destroy
#Can I do this?
belongs_to :product_variant, :dependent => :destroy
has_attached_file :shot, :styles => { :medium => "637x471>",
:thumb => Proc.new { |instance| instance.resize }},
:url => "/shots/:style/:basename.:extension",
:path =>":rails_root/public/shots/:style/:basename.:extension"
validates_attachment_content_type :shot, :content_type => ['image/png', 'image/jpg', 'image/jpeg', 'image/gif ']
validates_attachment_size :shot, :less_than => 2.megabytes
### End Paperclip ####
def resize
geo = Paperclip::Geometry.from_file(shot.to_file(:original))
ratio = geo.width/geo.height
min_width = 142
min_height = 119
if ratio > 1
# Horizontal Image
final_height = min_height
final_width = final_height * ratio
"#{final_width.round}x#{final_height.round}!"
else
# Vertical Image
final_width = min_width
final_height = final_width * ratio
"#{final_height.round}x#{final_width.round}!"
end
end
end

If I were to implement this, I think I would do it as a polymorphic relationship. So in product.rb and product_variant.rb:
has_many :product_shots, :dependent => :destroy, :as => :pictureable
And in product_shot.rb:
belongs_to :pictureable, :polymorphic => true
Now either product and product_variant can have as many (or as few) product_shots as they want, and both are accessible as product.product_shots and product_variant.product_shots. Just make sure you set up your database correctly -- your product_shots table will need pictureable_type and pictureable_id for this to work.

Related

Translating Database Models to Models on Ruby On Rails

I am beginning Ruby On Rails through a purchase/resale platform project at school. I'm having an issue with my models when I try to translate them from my relational model.
Firstly, I've modelled my database. Here is simplified the entity-relationship model :
I've then translated it in a relational model :
Finally, I've implemented it in Ruby On Rails.
I've implemented a model Client :
class Client < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Seller :
class Seller < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Article
class Article < ApplicationRecord
attr_accessor :quantity
validates :quantity, :presence => true
has_one :purchasing, :dependent => :destroy
has_one :client, :through => :purchasings
has_one :seller, :through => :purchasings
end
I've implemented a model Purchasing :
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
end
I've modified the Purchasing database migration :
class CreatePurchasing < ActiveRecord::Migration[5.1]
def change
[...]
add_index :purchasings, :client_id
add_index :purchasings, :seller_id
add_index :purchasings, :article_id
add_index :purchasings, [:client_id, :seller_id], :unique => true
end
def down
[...]
end
end
I know this is incorrect because when I execute the following code on the Rails console :
cl1 = Client.create(:name => "John")
cl2 = Client.create(:name => "James")
sel1 = Seller.create(:nom => "Jack")
sel2 = Seller.create(:nom => "Jil")
a1 = Article.create(:quantity => 5)
p1 = Purchasing.new(:client => cl1, :client_id => cl1.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p1.save
p2 = Purchasing.new(:client => cl2, :client_id => cl2.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p2.save
p2.save returns true whereas an article can't be sold by a same seller and bought by two clients different.
You are adding the index on incorrect columns on purchasings table. As per the requirement, the article_id and seller_id should not get repeated ideally. So you actually need is a uniqueness constraint on seller_id, and article_id column. You can do so by creating an unique index on the composition of two columns seller_id, and article id on the database layer. You should also add the application layer validation on the purchasing model.
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
validates :article_id, uniqueness: {scope: :seller_id}
end
Now you should also write a database migration to add the unique index on these two columns.
class AddUniquenessConstraintInPurshasing < ActiveRecord::Migration
def change
add_index :purchasings, [:article_id, :seller_id], :unique => true
end
end

Rails_admin has many through is deleting both :through modals

I have campaign, campaign_commissions, applicant and applicant_commissions modals.
campaign_commissions are all commissions available for a campaign and applicant_commissions are the applicants instance of those commissions.
When I delete an applicant, rails_admin also wants to delete both campaign_commissions and applicant_commissions. I only wish for applicant_commissions to be destroyed.
How can I specify this to happen?
My Models:
applicant.rb
class Applicant < ActiveRecord::Base
enum hired: [:pending, :declined, :awaiting_post, :accepted]
attr_accessor :post_url
belongs_to :post
belongs_to :campaign
belongs_to :site
has_many :applicant_commissions
accepts_nested_attributes_for :applicant_commissions
has_many :campaign_commissions, through: :applicant_commissions
scope :accepted, -> { where('hired == ?', 3) }
scope :declined, -> { where('hired < ?', 1) }
scope :pending, -> { where('hired < ?', 0) }
scope :awaiting_post, -> { where('hired < ?', 2) }
validates :campaign_id, :presence => true
#validates :site_id, :presence => true
validates :applicant_commissions, :presence => true
validates :campaign_id, uniqueness: { scope: :site_id,
message: "We already have an application from you" }
applicant_commission.rb
class ApplicantCommission < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
belongs_to :applicant
belongs_to :campaign_commission
has_many :invoice_parts
#delegate :user, to: applicant
end
campaign_commission.rb
class CampaignCommission < ActiveRecord::Base
belongs_to :campaign
belongs_to :commission
has_many :applicant_commissions
has_many :applicants, through: :applicant_commissions
end
campaign.rb (not involved in the issue, but added for context)
class Campaign < ActiveRecord::Base
has_attached_file :image, :styles => { :default => "500x500>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
belongs_to :programme
belongs_to :user
has_many :applicants
has_many :campaign_commissions
has_many :commissions, through: :campaign_commissions
has_many :images, as: :imageable
scope :front, -> { limit(4) }
end
applicant_commissions reaches through to the campaign commissions via this relationship.
has_many :campaign_commissions, through: :applicant_commissions
I would suggest this to sever the tie to solve the problem you presented.
has_many :campaign_commissions
And, I didn't see this in your sample model, but for answer completeness I'll include it here. If you don't include the destroy attribute, the dependent record (or association dependent) will not be deleted.
has_many :applicant_commissions, :dependent => :destroy
I don't think you can have the has many through relationship both ways, as live during application run but suspend the relationship when you want to delete the parent record. But maybe someone will come along with an answer and a way to do that.

How to validate attachment in nested model?

I have a Design model that has two Paperclip attachments inside two associations (full_image and preview). I want to be able to save designs only when both full_image and preview have valid files but can't seem to be able to make it work. Right now this is what I expected to work, but it doesn't just doesn't validate the attachments when I submit the form.
class Design < ActiveRecord::Base
has_one :full_image, :as => :assetable, :class_name => "FullImage", :dependent => :destroy
has_one :preview , :as => :assetable, :class_name => "Preview" , :dependent => :destroy
accepts_nested_attributes_for :full_image, :preview
validates_associated :preview, :full_image
end
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
delegate :url, :to => :attachment
end
class FullImage < Asset
has_attached_file :attachment
validates_attachment_presence :attachment
end
class Preview < Asset
has_attached_file :attachment
validates_attachment_presence :attachment
end
Could someone please suggest what I should be doing?
Try :
validates :attachment, :presence => true
inside the associated model instead of validates_attachment_presence
Here is how I got it working
class Design < ActiveRecord::Base
has_one :full_image, :as => :assetable, :class_name => "FullImage", :dependent => :destroy
has_one :preview , :as => :assetable, :class_name => "Preview" , :dependent => :destroy
accepts_nested_attributes_for :full_image, :preview
validates_presence_of :preview
validates_presence_of :full_image
end
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
delegate :url, :to => :attachment
end
class FullImage < Asset
has_attached_file :attachment
end
class Preview < Asset
has_attached_file :attachment
end

Nested forms dont work in rails_admin

Brand has many images is my association. Image uses paperclip
In Rails Admin, I want to add images when i add a brand
class Brand < ActiveRecord:Base
has_many :images, as=> :imageable
end
class Image < ActiveRecord:Base
attr_accessor :image_thumb
attr_accessible :image, :imageable_id, :imageable_type, :image_thumb
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
belongs_to :imageable, :polymorphic => true
end
But this is what i get
How can i achieve my goal?
I need to add attr_accessible
class Brand < ActiveRecord::Base
attr_accessible :name, :images_attributes
has_many :products
has_many :images, :as => :imageable
accepts_nested_attributes_for :images, :allow_destroy => true
end
And solution is when you user acts_as_taggable, to add to your rails_admin.rb config file next code:
field :tag_list do
label "Tag"
end
Or 'image_list', depends on what you are use.

How do I validate the uniqueness of a has_many :through join model?

I have users and issues joined by a votership model. Users can vote on issues. They can either vote up or down (which is recorded in the votership model). First, I want to be able to prevent users from casting multiple votes in one direction. Second, I want to allow users to cast the opposite vote. So, if they voted up, they should still be able to vote down which will replace the up vote. Users should never be able to vote on an issue twice. Here are my files:
class Issue < ActiveRecord::Base
has_many :associations, :dependent => :destroy
has_many :users, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :users, :through => :voterships
belongs_to :app
STATUS = ['Open', 'Closed']
validates :subject, :presence => true,
:length => { :maximum => 50 }
validates :description, :presence => true,
:length => { :maximum => 200 }
validates :type, :presence => true
validates :status, :presence => true
def cast_vote_up!(user_id, direction)
voterships.create!(:issue_id => self.id, :user_id => user_id,
:direction => direction)
end
end
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
end
class VotershipsController < ApplicationController
def create
session[:return_to] = request.referrer
#issue = Issue.find(params[:votership][:issue_id])
#issue.cast_vote_up!(current_user.id, "up")
redirect_to session[:return_to]
end
end
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
has_many :associations, :dependent => :destroy
has_many :issues, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :issues, :through => :voterships
end
You would put the uniqueness constraint on the Votership model. You don't need to put validations on the association itself.
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
validates :issue_id, :uniqueness => {:scope=>:user_id}
end
This means a user can only have a single vote on a given issue (up or down).
Relationship models:
class Person
has_many :accounts
has_many :computers, through: :accounts
end
class Account
belongs_to :person
belongs_to :computer
scope :administrators, -> { where(role: 'administrator') }
end
class Computer
has_many :accounts
has_many :people, through: :accounts
end
This is how it is called
person.accounts.administrators.map(&:computer)
We can do this better using ActiveRecord::SpawnMethods#merge!
person.computers.merge(Account.administrators)
Ref: https://coderwall.com/p/9xk6ra/rails-filter-using-join-model-on-has_many-through

Resources