I am learning Rails and ActiveRecord stuff but I am kind of stuck on this small problem.
Lets say you I a product (Stock) and every product has a colour, normally the right way is to setup an association. Please take a look at the below code:
class Stock < ActiveRecord::Base
attr_accessible :size, :colour_id
end
class Colours < ActiveRecord::Base
belongs_to :stock
end
Actually what I want to be able to do is
p #stock_item.colour.name
# But I get this error
SQLite3::SQLException: no such column: colours.stock_id: SELECT "colours".* FROM "colours" WHERE "colours"."stock_id" = 1 LIMIT 1
When in fact the query should have been:
SELECT "colours".* FROM "colours" WHERE "colours"."id" = "stock"."colour_id"
As colours are all unique in reality a property of the Stock item. How can I setup an association this way so that I could do:
p #stock_item.colour.name
> Red
Thanks.
Umer
you need to add a belongs_to association to stock and not to colour since stock has the colour_id
class Stock < ActiveRecord::Base
belongs_to :colour
end
and you most probably want a has_many association on colour
class Colour < ActiveRecord::Base
has_many :stocks
end
Related
I have a Product model and each product can have many options, such, as size and color. Each Option can also have many Choices. So the "Size" Option might have "Small," "Medium," and "Large" as Choices and the "Color" option might have "Red" and "Blue" as choices.
Using Simple Form I'm essentially trying to do something like this on the Product form:
The problem is that if the user has multiple product options (Such as size and Color), it only gives them one radio button across each set of Options. So they could select "Blue" but not "Blue" and "XL," for instance.
The other thing I could do is use as: :check_boxes instead of as: :radio_buttons but then the user could select more than one color (e.g. red and blue), when only one choice should be allowed for each option.
So what is the best "Rails" way to validate a limit on each instance of the association, but not the association itself? I could do this in javascript on the client side if I have to, but that seems less safe than having the validation on the server side.
Plus the Product should be able to have many Choices. So it's not really a validation on the association between Products and Choices, but rather a validation on limiting to 1 Choice for each set of choices that are available through the Options model.
For instance, a T-Shirt might be Red and XL, but it shouldn't be allowed to be Red & Blue + Small & XL?
Here are my models:
class Product < ApplicationRecord
has_many :choices, through: :options
end
class Option < ApplicationRecord
belongs_to :product
has_many :choices
end
class Choice < ApplicationRecord
belongs_to :option
end
If a customer is suppose to order/select a product with specifications, you may actually need a joining model (Selection/Order) instead of applying validations to the Product model. The Product model seems like it's there just for you to setup the options and choices that the user can select for that product.
If that's the actual case here, you would just create the joining model and just set it up with a polymorphic "feature." Something like this:
class Order
belongs_to :product
belongs_to :featurable, polymorphic: true
belongs_to :user
validates_inclusion_of :featurable_type, in: %w[Option Choice]
end
Newer Rails versions will validate that the belongs_to fields are present.
I say polymorphic because I'm assuming that the option may not have choices and sometimes you could just select the option itself. If all options will have choices, then just change the belongs_to :featurable to belongs_to :choice and remove the inclusions validation. The belongs_to :user is there since I assume a specific user would put in this order/selection.
If you may have multiple option choices selected for your product, then you may structure it more like this:
class ProductOrder
belongs_to :product
belongs_to :user
has_many :choice_selections
end
class ChoiceSelection
belongs_to :product_order
belongs_to :featurable, polymorphic: true
validates_inclusion_of :featurable_type, in: %w[Option Choice]
validate :unique_option
def option
featurable.is_a?(Option) ? featurable : featurable.option
end
def unique_option
return unless product_order.choice_selections.find_by(option: option).present?
errors.add(:base, 'choice option must be unique')
end
end
If all options will have choices:
You wouldn't need a polymorphic association.
class ProductOrder
belongs_to :product
belongs_to :user
has_many :choice_selections
end
class ChoiceSelection
belongs_to :product_order
belongs_to :choice
belongs_to :option
validates_uniqueness_of :option, scope: :product_order
end
However, to answer the question you've posted above:
The first thing I'd do is create a custom validation in the Product model.
Be sure to add the has_many :options line in the Product model so it looks more like this:
class Product < ApplicationRecord
has_many :options
has_many :choices, through: :options
end
Otherwise, that through may not work.
Then, add the validation like so:
class Product < ApplicationRecord
# ...
validate :one_choice_per_option
private
def one_choice_per_option
if options.any? { |option| option.choices.count > 1 }
errors.add(:options, 'can only have one choice')
end
end
# ...
end
Please note that this validation will prevent you from creating more than one choice for your product options. I do hope it gives you a better idea on how to create custom validations. I would highly recommend reevaluating your database structure to separate product/option/choice setup and user selections.
If this validation is something that you may use in other models, you may want to consider making is a validator.
I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }
product.rb
has_many :votes
vote.rb
belongs_to :product
Every time, i use sorting in my index controller:
index_controller.rb
def index
#products = Product.all.sort { |m| m.votes.count }
end
So, i think it would be good to cache votes count for each product (create additional column votesCount in products table)?
If yes, can i preform that using before_save and before_delete callbacks in vote.rb model?
Or what is the best practice method?
Give me some examples please.
I guess you are looking for counter_cache
The :counter_cache option can be used to make finding the number of belonging objects more efficient
Consider these models:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. In the case above, you would need to add a column named orders_count to the Customer model
I plan on making a genotype calculator in the future. I intend for this calculator to eventually be able to compute the following from a pairing: probability of color listing all possibilities, genotype.
I am wanting to make a dropdown menu/text field combination on a very simple webpage to learn how it works so that I can continue my project and hopefully meet this goal. I have searched and tried to figure this out, but I am pretty lost. Currently in my database I have a table called "colors" with the following schema:
id
angora_color
genotype
created_at
updated_at
I do not intend for users to be able to add data to this form. I want them to be able to select a color from the dropdown box, and get the genotype in a text field below it.
My code so far is as follows:
class Color < ActiveRecord::Base
has_one :genotype
end
class Genotype < ActiveRecord::Base
has_one :color
end
index.html.erb:
<h2>Placeholder for Genotype List..</h2>
class PagesController < ApplicationController
def index
end
end
I appreciate any help.
Are you sure you only want a has_one relationship? Wouldn't a Genotype have many colors? and Colors can be part of many Genotypes?
You also can't have both models declare has_one. One model has to belong to the other. And the one that belongs_to should have the foreign key as <model_name>_id e.g. genotype_id. In your table you only put genotype. Rails looks for that _id.
What may be better here is to use has_many through. Create a join model such as genotypes_colors:
rails g model GenotypesColor genotype_id:integer color_id:integer
Then change your code to look like:
class Genotype < ActiveRecord::Base
has_many :genotypes_colors
has_many :colors, through: :genotypes_colors
end
class GenotypesColor < ActiveRecord::Base
belongs_to :genotype
belongs_to :color
end
class Color < ActiveRecord::Base
has_many :genotypes_colors
has_many :genotypes, through: :genotypes_colors
end
Now you can correctly relate a Genotype to its Colors. You can use fields_for in either model's forms to create the genotypes_color association that will relate a Genotype to any Color or vice versa. If this sounds about right let me know and I can further help on how to do the forms.
Right now my migration reads as follows:
class CreateColors < ActiveRecord::Migration
def change
create_table :colors do |t|
t.string :angora_color
t.string :genotype
t.timestamps
end
end
end
I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end