I have an Item model, that can be clothing, shoes, or bags (category).
I also have the Size model, that represents the Size of the Item.
The issue is:
For clothing, size can be: XS, S, M, L, XL
For shoes, size can be: 9, 9.5, 10, 10.5
Also, the shoe size depends if it is UK or US or European, so a UK 6 is a US 7 and European 40.
How should I model this?
Everything as a single Size model?
Should I have child models?
Or should I have many different models (ShoeSize, ClothingSize)?
Thanks in advance.
Is there any reason you require an 'Item' catch-all model?
The two ways I might do this are:
1) I would either have separate models for clothes, shoes and bags. Each model would have it's own individual size attribute.
I would also do their size attributes as a collection with "US 6" etc as separate options.
or
2) If you want to keep them all under one 'Item' model . Make their type (i.e. shoes, clothes, bags) an enumeration (Example included below). Then I would make a different column in my table for each type of size for example: Item would have an attribute that is :clothes_size that is a collection with your S,M,L,XL and then a :shoes_size with it's own selection etc.Then you can just display the field that is necessary for that item.
Enumerations look like this:
class Item < ActiveRecord::Base
enum type: [:shoes, :clothing, :bags]
end
What the enumeration will do is make it so you have an attribute to put them into categories; mapping each one to an integer in the db. (0=shoes, 1=clothing, 2=bags)
Then you will need to run a migration.
rails g migration AddTypeToItem type:integer
It will also auto generate a bunch of super cool built in methods for you to sort and find your objects by type, such as
#item.shoes? etc. Read more about them in the Rails API.
You could put the item category as an enumeration as #ChiefRockaChris suggested, then I would do this:
make the size attribute an integer and map these integers to a different set of strings according to item category. I would come up with a standard measurement unit for shoe size (like mm) then have another function that maps that measurement to UK, US or European.
This code would go in your Item class
def self.sizes
{
clothing: {1: 'XS', 2: 'S', 3: 'M', 4: 'L', 5: 'XL'},
shoe: {1: '35mm', 2: '40mm', 3: '45mm'}
}
end
def self.shoes_size_type
{
UK: {'35mm' => '6', '40mm' => '7', '45mm' => '8'},
US: {'35mm' => '7', '40mm' => '8', '45mm' => '9'},
Europe: {'35mm' => '35', '40mm' => '40', '45mm' => '45'}
}
end
def self.size_parsed
sizes[category][size]
end
def self.shoe_size(country)
shoe_size_type[country][size_parsed] or size_parsed
end
Related
Say that I have these tables/associations :
Product has_many :keywords, :through => :product_keywords
Keyword has_many :products, :through => :product_keywords
And the ProductKeyword table is :
=> ProductKeyword(id: integer, keyword_position: integer, product_id: integer, keyword_id: integer)
Let's picture the prk variable as a list of products. Each product can have many keywords and keyword is defined as :
=> Keyword(id: integer, phrase: text)
Multiple keywords could contain the same phrases in terms of the text of the Keyword, but they are different database entries.
What I want to do is create a table with all the keywords of all the products and for each keyword calculate the sum of all those keywords that had a keyword_position < 10.
So if 5 products have the same keyword phrase(say "beach") and thus 5 different entries and their respective ProductKeyword keyword_positions are [1, 6, 11, 13, 3], I want that keyword to return a unique entry associated with the sum of less than 10 keyword_positions, which in this case would be 3.
I have tried a few different things, but end up confusing myself. What is the proper way to do this ?
I believe the below code should produce the data you're looking for:
Keyword
.joins(:product_keywords)
.where(ProductKeyword.arel_table[:keyword_position].lt(10))
.group(Keyword.primary_key)
.select(
Keyword.arel_table[Arel.star],
Arel.star.count.as('product_count')
)
This will run the following SQL query (syntax may vary depending on your database):
SELECT "keywords".*, COUNT(*) AS product_count
FROM "keywords"
INNER JOIN "product_keywords" ON "product_keywords"."keyword_id" = "keywords"."id"
WHERE "product_keywords"."keyword_position" < 10
GROUP BY "keywords"."id"
That will return a list of Keyword records; you can run .product_count on each of these records to determine how many associated ProductKeyword records there are with a keyword_position value of less than 10.
You could then create a table to hold the data produced by the above code.
If you wanted to determine the count for a specific Keyword record without running that whole query, the following code should produce that count:
my_keyword.product_keywords.where(ProductKeyword.arel_table[:keyword_position].lt(10)).count
Let's say I have a table for a model Score, and a table for a model Multiplier. Both of these models have attributes date and value. I want to now create a new object of a different model Total, that corresponds to the date for Score and Multiplier. Here's the idea, though I don't think it's a good solution:
def create
#scores = Score.all
#scores.each do |score|
Total.create(:date => score.date, :value => score.value + Multiplier.find_by_date(score.date).value)
end
end
Essentially, how can I add the values of two fields from separate models to create a third object of a different model, in an elegant way? Thanks in advance!
I have two models:
class Country
has_many :competitions
end
class Competition
belongs_to :country
end
Competition class has a position attribute. Admin can sort competitions by position. I want to sort countries with the minimum position of its competitions. I also want to joing competitions with country. How can i achieve that?
I want an output like:
X Country: (is at first order because Xcomp1's position is 1)
Xcomp1 (position: 1)
Xcomp2 (position: 12)
A Country:
Acomp1 (position:2)
Acomp2 (position:3)
Z Country: (is at last position because minimum position of its competitions are higher than other ones)
Zcomp1 (position:5)
I think you'd have to:
Country.order("(select min(position) from competitions where competitions.country_id = countries.id) asc")
I'm not sure how that syntax holds up across different RDBMSs -- should be good on PostgreSQL and Oracle
I have a product that has many variants, those variants have two attributes: Size and Color.
I want to query for the Variant based on the two attributes I pass in - I got it to work with following:
variants = Spree::Variant.joins(:option_values).where(:spree_option_values => {:id => size.id}, :product_id => prod.id).joins(:option_values)
variant = variants.select{|v| v.option_values.include?(size)}
From my understanding, the select method more or less iterates through the array, which is kinda slow. I would rather have a query that finds the variant directly based on those two attributes.
I tried the following:
Spree::Variant.joins(:option_values).where(:spree_option_values => {:id => size.id}, :product_id => prod.id).joins(:option_values).where(:spree_option_values => {:id => color.id})
but this only ended up in returning an empty array.
How would I go about this?
Edit: Here are the product, variant and option_values models:
Product:
https://github.com/spree/spree/blob/master/core/app/models/spree/product.rb
Variant:
https://github.com/spree/spree/blob/master/core/app/models/spree/variant.rb
OptionValue: https://github.com/spree/spree/blob/master/core/app/models/spree/option_value.rb
OptionType: https://github.com/spree/spree/blob/master/core/app/models/spree/option_type.rb
Updated 2: you're right, this is not what you looking for.
So you can:
1) Build SQL subquery: (if joined table has size and has color at the same time then return TRUE). How quick it will be working - is a question...
2) Imagine you've created a model "ValuesVariants" for table "spree_option_values_variants" and kicked out habtm (replace with 2 has_manys + 2 has_manys through). Now you can search ValuesVariants with (option_type_id = size_id||color_id AND variant_id IN (array of product's variant ids)), extracting matched variants. It can be quick enough...
3) You can use :includes. so associated objects loaded into the memory and the second search do by array methods. In this case the concern is in memory usage.
I have a state field that stores the value as 2 characters. For example, Alabama is saved as AL, Alaska is saved as AK, Arizona is saved as AZ, etc. In the show.html.erb, how do I display the long name for the state such as Alabama instead of just showing AL? Is this possible or should I just store the long name in the database such as Alabama, Alaska, Arizona, etc?
Write a method that would output long name of a stateand call it in show.html.erb
some_model.rb:
SomeModel < ActiveRecord::Base
STATE_CODES = {
"AL": "Alabama", "AK": "Alaska",
# add remaining 50
}
def state_human_name
STATE_CODES[self.state]
end
show.html.erb:
<%= record.state_human_name %>
EDIT: It does not help to store full names of states in your database -- you'll need short forms at least somewhere and therefore would need to add mapping between short and long forms anyway.
Is there a reason for using the 2 letter codes (e.g. a legacy database)? If not I would stick to the usual ActiveRecord idiom and have a separate "states" table linked by id. If you need the 2 letter code for display purposes, printing address labels or whatever then add add a 'state_code' attribute to the states table but don't use it as a primary key.
I put this in a comment, but I've decided it's sufficiently different that it warrants an answer.
When you're deciding where to keep your state map, consider whether you'll ever need to ship things to Canada, or further afield. If so, it's worth the effort to set up a states table, linked to a countries table.
And anyway, if your data rarely changes, it's less issue-prone to put it in the database, because code changes far more often. More frequent changes = more opportunities to mess it up. Plus, it's then trivial to sort as you like.
class State < ActiveRecord::Base
def self.get_states
##states || State.find(:all,
:include => :country,
:order => 'countries.name, long_name')
end
end
Tilendor, I notice that if I use STATE_CODES.invert, the drop-down menu selection would get out of order. For example, the first five lines of my option list is shown below:
New Hampshire
Ohio
Colorado
Minnesota
Alabama
...
In my STATES_CODES hash, I have the following listed in the order below:
"AL" => "Alabama",
"AK" => "Alaska",
"AZ" => "Arizona",
"AR" => "Arkansas",
"CA" => "California",
...
Is there a way to have the options listed in the form in the same order as the STATES_CODES? Maybe sort them alphabetically?