How to create correct database scheme of cooking site? - ruby-on-rails

I want to make a cooking site but don't know the correct was to build database.
My models are: Recipe and Ingredient.
Ingredient in recipe should be autocomplete field. The problem is that user can put there any text. ("cucumber" or "cucamber") and it will be different ingredients.
I want to make a search by ingredients and links to them. What is the best way to do it?

A recipe has many items, which in turn keeps a reference to an ingredient, an amount and a measure type. So you can go with:
rails generate model Recipe name:string description:text
rails generate model Item recipe:references ingredient:references amount:decimal measure:string
rails generate model Ingredient name:string
and then add to your classes:
class Recipe < ActiveRecord::Base
has_many :items
has_many :ingredients, :through => :items
# this allows things like #recipes = Recipe.using("cucumber")
scope :using, lambda do |text|
joins(:ingredients).where("ingredients.name LIKE ?", "%#{text}%")
end
end
class Item < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
VALID_MEASURES = %w[oz kg tbsp] # use for "select" tags in forms
validates :measure, :inclusion => VALID_MEASURES
end
class Ingredient < ActiveRecord::Base
belongs_to :item
end
From here you start building your views, autocomplete, whatever your imagination allows.

Related

How to validate a limit on each instance of the association, but not the association itself in Rails

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.

Create Rails scope comparing fields on two tables

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') }

Rails: How can I put the sections together to form a book automatically?

I'm a freshman to learn Rails and working on my first project about "online book writing".
I've already made the MVC of user,book and section. Association like this:
class User < ActiveRecord::Base
has_many :sections , dependent: :destroy
end
class Section < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
class Book < ActiveRecord::Base
has_many :sections
end
So I made a relationship between user and section,but no relationship between user and book. for example,user.1 write section.1 of book.1 ,the user.2 write section.2 of book.1.
Then I want to use all the sections of book.1 which written by different users,to form a book.1 automatically. (connect the text field directly in one text field,book_content is sum of section_content)
What should I do in the views or models?
You'll have to add an index field to manage the order of the sections. But for now you can do this:
<%= render book.sections %>
And then you'll need to define a partial in sections/_section.html.(erb.haml.whatever)

How to create an association that sets join table attributes automatically?

I am totally confused about how I should go about "the rails way" of effectively using my associations.
Here is an example model configuration from a Rails 4 app:
class Film < ActiveRecord::Base
# A movie, documentary, animated short, etc
has_many :roleships
has_many :participants, :through => :roleships
has_many :roles, :through => :roleships
# has_many :writers........ ?
end
class Participant < ActiveRecord::Base
# A human involved in making a movie
has_many :roleships
end
class Role < ActiveRecord::Base
# A person's role in a film. i.e. "Writer", "Actor", "Extra" etc
has_many :roleships
end
class Roleship < ActiveRecord::Base
# The join for connecting different people
# to the different roles they have had in
# different films
belongs_to :participant
belongs_to :film
belongs_to :role
end
Given the above model configuration, the code I wish I had would allow me to add writers directly to a film and in the end have the join setup correctly.
So for example, I'd love to be able to do something like this:
## The Code I WISH I Had
Film.create!(name: "Some film", writers: [Participant.first])
I'm not sure if I'm going about thinking about this totally wrong but it seems impossible. What is the right way to accomplish this? Nested resources? A custom setter + scope? Something else? Virtual attributes? thank you!
I created a sample app based on your question.
https://github.com/szines/hodor_filmdb
I think useful to setup in Participant and in Role model a through association as well, but without this will work. It depends how would you like to use later this database. Without through this query wouldn't work: Participant.find(1).films
class Participant < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
class Role < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
Don't forget to give permit for extra fields (strong_parameters) in your films_controller.rb
def film_params
params.require(:film).permit(:title, :participant_ids, :role_ids)
end
What is strange, that if you create a new film with a participant and a role, two records will be created in the join table.
Update:
You can create a kind of virtual attribute in your model. For example:
def writers=(participant)
#writer_role = Role.find(1)
self.roles << #writer_role
self.participants << participant
end
and you can use: Film.create(title: 'The Movie', writers: [Participant.first])
If you had a normal has_and_belongs_to_many relationship i.e. beween a film and a participant, then you can create a film together with your examples.
As your joining model is more complex, you have to build the roleships separately:
writer= Roleship.create(
participant: Participant.find_by_name('Spielberg'),
role: Role.find_by_name('Director')
)
main_actor= Roleship.create(
participant: Participant.find_by_name('Willis'),
role: Role.find_by_name('Actor')
)
Film.create!(name: "Some film", roleships: [writer, main_actor])
for that, all attributes you use to build roleships and films must be mass assignable, so in a Rails 3.2 you would have to write:
class Roleship < ActiveRecord::Base
attr_accessible :participant, :role
...
end
class Film < ActiveRecord::Base
attr_accessible :name, :roleships
...
end
If you want to user roleship_ids, you have to write
class Film < ActiveRecord::Base
attr_accessible :name, :roleship_ids
...
end
Addendum:
Of cause you could write a setter method
class Film ...
def writers=(part_ids)
writer_role=Role.find_by_name('Writer')
# skiped code to delete existing writers
part_ids.each do |part_id|
self.roleships << Roleship.new(role: writer_role, participant_id: part_id)
end
end
end
but that makes your code depending on the data in your DB (contents of table roles) which is a bad idea.

HABTM relationship, How to input multiple values for id?

I have a User model which has_many Portfolios, which has_many Assets which has_and_belongs_to_many AssetHistories.
Basically User 1 might have Google in their portfolio and User 2 might also have Google in their portfolio. Why populate the database with duplicate lines of stock price history for Google when I can have a many-to-many (HABTM) relationship. However what throws me off is what to put for asset_id in the AssetHistory model when it will be multiple values. I.e. it needs to reference both user 1 and user 2. User 1's Google might be asset.id 1 and User 2's Google might be asset.id 2. Therefore how do the entries in the AssetHistory model reference both the ids?
It seems pretty clear that asset_id can't be 2 values simultaneously but I can't wrap my head around this. Am I supposed to use a foreign_key and make Google the key? If so, I still have issues in my Asset model for what entry to put for Asset_History_id, because the asset Google, will have maybe 30 lines of stock price history. Each stock price history would be a different Asset_History_id.
Can someone help explain what I'm doing wrong?
Note that I am using after_save in my asset model to populate the asset price histories. I.e. when someone adds an Asset, it populates the asset_history, but it doesn't populate the asset_history_id field in the Asset model and it doesn't populate the asset_id in the AssetHistory model because I'm at a lost on what to do there.
My asset model has:
class Asset < ActiveRecord::Base
attr_accessible :asset_symbol, :shares, :cost, :date_purchased, :asset_history_id
belongs_to :portfolio
has_and_belongs_to_many :asset_histories
after_save populatepricehistory
private
def populatepricehistory
#uses an api to download price data as an array and imports it to AssetHistory...
#I expect something should go here to fill out the asset_history_id field in the Asset Model
#while simultaneously filling out the asset_id in the AssetHistory model
end
end
Asset History model
class AssetHistory < ActiveRecord::Base
attr_accessible :close, :date, :asset_id, :asset_symbol
has_and_belongs_to_many :assets
end
Migration for AssetHistoryAsset join table
class AssetHistoryAssetJoinTable < ActiveRecord::Migration
def up
create_table :asset_histories_assets, :id=> false do |t|
t.integer :asset_id
t.integer :asset_history_id
end
end
def down
drop_table :asset_histories_assets
end
end
My suggestion would be this:
class User < ActiveRecord::Base
has_many :assets, :through => :porfolios
has_many :porfolios
end
class Porfolio < ActiveRecord::Base
has_many :assets
has_many :users
end
class Asset < ActiveRecord::Base
has_many :users, :through => :portfolios
has_many :portfolios
has_and_belongs_to_many :asset_histories
end
By the way, do you really need a many-to-many relationship between Asset and AssetHistory? I would imagine each instance of AssetHistory to refer to only one Asset, probably by means of belongs_to :asset / has_one :asset_history.

Resources