How do I seed a belongs_to association? - ruby-on-rails

I would like to seed my Products and assign them to a specific User and Store.
Product.rb
class Product < ActiveRecord::Base
belongs_to :user
belongs_to :store
def product_store=(id)
self.store_id = id
end
end
Note: Store belongs_to Business (:business_name)
Seed.rb
This is my basic setup:
user = User.create(:username => 'user', :email => 'user2#email.com')
store = Store.create(:business_name => 'store', :address => 'Japan')
I attempted these but they did not work:
# This gives random ID's ranging from 1 to 4425!?
user.products.create([{:name => "Apple", :product_store => Store.find_by_address('San Francisco, USA')}])
# This gives me undefined method 'walmart'.
user.store.products.create([ {:name => "Apple"} ])
Is there a way to set the ID's so I can associate my Products to a Store and User?
UPDATE -
I have tried the answers below and still came out unsuccessful. Does anyone know of another way to do this?

Although it sounds like you found a workaround, the solution may be of interested to others.
From your original seeds.rb
user = User.create(:username => 'user', :email => 'user2#email.com')
store = Store.create(:business_name => 'store', :address => 'Japan')
Create the store
Store.create({
user_id: user.id
store_id: store.id
}, without_protection: true)
In the original code snipped "user" and "store" variables are declared. The code assigns user_id / store_id (the model columns inferred by the belongs_to relationship in the Store model) to the id values that are present in the "user" and "store" variables.
"without_protection: true" turns off bulk assignment protection on the id fields. This is perfectly acceptable in a seeds file but should be used with extreme caution when dealing with user provided data.

Or alternatively create your stores.
Then extract the correct one
e.g.
store = Store.find_by_business_name('Test Store')
and then create it based on that
e.g.
store.products.create(:product_name => "Product Test", :price => '985.93')
This will then set the relationship id for you,

If I'm not mistaken, you're just trying to do this.
user = User.create(:username => 'usertwo', :email => 'user2#email.com')
walmart = Store.create(:business_name => 'Walmart', :address => 'San Francisco, USA')
user.products.create(:name => 'Apple', :store => walmart)
Anything else required here that I'm not seeing?

Try doing this
store_1 = Store.new(:business_name => 'Test Store',
:address => 'Test Address',
:phone_number => '555-555-555')
store_1.id = 1
store_1.save!
The trick is not to set the id within the hash as it is protected.
Scott

What I did was update the particular products to a certain user, see this question:
Can I update all of my products to a specific user when seeding?

You could just create a series of insert satements for this "seed migration", including the record Id for each user, store, product etc. You might have to update database sequences after this approach.
Another approach
Create the initial records in you Rails app, through the GUI / web.
Then use something like Yaml-db. So you can dump the data to a yaml file. You can now edit that file (if necessary) and use that same file to seed another instance of the db with "rake db:load"
Either way.... You know the Ids will not be shifting around on you when these objects are created in the new db instance.
I'm sure there are other ways to do this... Probably better ones, even.
Here is a link to a write-up I did a while back for using yaml_db to seed an oracle database
http://davidbharrison.com/database_seeding_oracle

Try this:
User.destroy_all
Product.destroy_all
user = User.create!([{:username => 'usertwo', :email =>'user2#email.com'},
{:username => 'userthree', :email => user3#email.com}])
user.each_with_index do |obj, index|
Product.create!([{ :product_name => 'product #{index}', :user_id => obj.id }])
end
The table would look like this:

Here's how I prefer to seed an association in rails 6
#teacher = Teacher.new(name: "John")
#student = #teacher.build_student(name: "Chris")
#teacher.save!
#student.save!

Related

Rails: Most efficient way to check if a new record already exists in DB

Lets say I have a model Product.
Once in a while I get a file containing new products, the issue is that some of them may already have been entered into the DB.
The data doesn't contain any unique key and the can come structured differently with different fields. What I can do is select from the DB according to all the data I have and if a product is found, not to save the one from the file.
Product.where(:name => p.name, :desc => p.desc, :source => "some source", [more fields])
So my question is if there is a better rails way to check if the record already exists?
Inserting to fail on some unique key, isn't such a good idea IMO but can work too.
You can use the exist? function like this :
if Product.where(:name => p.name, :desc => p.desc, :source => "some source", [more fields])
# do something
else
# do something else
end
You can also use the find_or_create_by if your goal is to create a new record if it does not exist like this :
Product.find_or_create_by(:name => p.name, :desc => p.desc, :source => "some source", [more fields])

populate random data in ruby on rails

I need help in populate some random data in my database table.
I have a list of 10 users in my system. My allergy table has the following fields:
id user_id name reactions status
I have the following allergies hash in a variable called allergy_hash.
{:reaction_name=>"Bleeding", :status=>"Death", :name=>"A"} {:reaction_name=>"Nausea", :status=>"Serious", :name=>"B"} {:reaction_name=>"Fever", :status=>"Death", :name=>"C"} {:reaction_name=>"Blistering", :status=>"Serious", :name=>"D"}
Here is what I have done so far:
def create_random_data
users.each do |user|
allergies.each do |allergies_hash|
Allergy.where(user_id: user.id).first_or_create(
allergies_hash )
end
end
end
What the above does is just inserts Bleeding, Death and A into the table for all users 1 to 10.
But I need to insert such that different users can have different values. Also some users can have more than one allergy and the associated reactions.
NOTE: I do not mean completely random. For example name 'A' should still have the associated status 'Death' and reaction_name 'Bleeding'.
Name 'B' should have the associated status 'Serious' and reaction 'Nausea'in the allergy table.
When creating the users, use sample on allergies_hash = [{:reaction_name=>"Bleeding", :status=>"Death", :name=>"A"}, {:reaction_name=>"Nausea", :status=>"Serious", :name=>"B"}, {:reaction_name=>"Fever", :status=>"Death", :name=>"C"}, {:reaction_name=>"Blistering", :status=>"Serious", :name=>"D"}]
Allergy.where(user_id: user.id).first_or_create(allergies_hash.sample)
UPDATE
I'll loop through the users instead, so for each user you attempt to add from 1 to 3 allergies from your allergies_hash
User.all.each do |user|
[1,2,3].sample.times do
user.allergies.where(allergies_hash.sample).first_or_create
end
end
I would recommend you to check Faker and Factory girl to populate some random data.
You can either seed data into your app by going to the seed file in the app/db directory
and do something like this
User.delete_all
Bill.delete_all
u1 = User.create(:email => "bob#aol.com", :password =>"a", :password_confirmation => "a")
b1 = Bill.create(:name => "rent", :description => "the rent", :amount => 10_000, :day => 1)
b2 = Bill.create(:name => "cable", :description => "the cable", :amount => 150, :day => 5)
or you can also use the Faker gem to generate fake data.
http://geekswithblogs.net/alexmoore/archive/2010/01/18/faker-gem---a-quick-and-dirty-introduction.aspx

Constructing a new object in Rails that is part of an active record association

This is just a simple question. I was trying to create a new object in Rails by passing in parameters to the constructor. However, when I execute the code, I get
SQLite3::SQLException: no such column: awards.user_id: SELECT "awards".* FROM "awards" WHERE "awards"."user_id" = 1
which means the object isn't being constructed properly. Should I be using create instead of new? That isn't working either.
def refresh_awards(user)
new_awards = []
if (user.karma < 40 ) #test award
a = Award.new(:name => "Nobody Award", :description => "From Jonathan", :category => "Community", :value => 1337, :level => 0, :handle => "nobody_award")
user.awards.append(a)
new_awards.append(a)
end
new_awards.each do |a|
flash[:notice] = "You received the " + a.name + "!"
end
end
Have you add has_many :awards to the User model? Have you added belongs_to :user to the Award model? Have you added the column user_id to the Award model (using a migration)? You'll need to do these three things to be able to use the user.awards method you're using. Read the Rails Guide on Associations for more detail.
Also, append isn't a Ruby method - the closest method would be <<. You would use it like this:
a = Award.new(:name => "Nobody Award", :description => "From Jonathan", :category => "Community", :value => 1337, :level => 0, :handle => "nobody_award")
user.awards << a
But you could neaten this into one line of code using the create method:
a = user.awards.create(:name => "Nobody Award", :description => "From Jonathan", :category => "Community", :value => 1337, :level => 0, :handle => "nobody_award")
EDIT: To create the user_id column in the Award model, run the following code from terminal (while in your app's directory):
rails generate migration AddUserIdToAward user_id:integer
rake db:migrate

Rails 3 - is possible to make IFs in a database query

I have a profile card of user that have registration in my forum.
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
:avatar => params[:person][:avatar].original_filename,
:city => params[:person][:city]},
{:id => params[:id]})
This is query for updating data in database. But here is a small problem - this will work only in a situation, if I the user send through form avatar (image). If not send avatar - that means the user already have uploaded avatar and the form send only name, sex, age and city. So in this case I'll get error in line :avatar => params[:person][:avatar].original_filename, -- and I would like to ask you for, if exist some elegant way, how to treat this moment.
I thought something like this:
if params[:person][:avatar]
avatar = ':avatar => params[:person][:avatar].original_filename,'
end
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
avatar
:city => params[:person][:city]},
{:id => params[:id]})
But unfortunately, this doesn't work... How you're solving similar situation?
Thank you.
Well, it seems, like your params[:person] keys are similar to your model fields. So why don't you just pass params[:person] to update_all?
Alternatively, you could create a hash person, initialize it the way you want and then pass it to update_all
person = { :name => params[:person][:name] ,
...
if params[:person][:avatar]
person[:avatar] = params[:person][:avatar].original_filename
end
Person.update(params[:id], person)
I've changed update_all to update, because update_all is used to update all the records (that match the condition), while update find's the record by it's ID.
But again, it's a bad practice and you have to type a lot of unnecessary code.
One more thing. update_all makes a direct DB call, which doesn't involve validations, callbacks etc.
So, if you don't have some special reason for this, you'd better do something like this:
#person = Person.find params[:id]
#person.update_attributes params[:person]
I really think, you should check this book out
Updated once again :)
You see, such things belong to your models, not controllers. You could define a setter in the model:
def avatar=(value)
write_attribute(:avatar, value.original_filename)
end

Implementing rental store in Rails: how to track an inventory state over time?

Let's say you're implementing rails app for a snowboard rental store.
A given snowboard can be in one of 3 states:
away for maintenance
available at store X
on loan to customer Y
The company needs to be able to view a rental history for
a particular snowboard
a particular customer
The rental history needs to include temporal data (e.g. Sally rented snowboard 0123 from Dec. 1, 2009 to Dec. 3 2009).
How would you design your model? Would you have a snowboard table with 4 columns (id, state, customer, store), and copy rows from this table, along with a timestamp, to a snowboard_history table every time the state changes?
Thanks!
(Note: I'm not actually trying to implement a rental store; this was just the simplest analogue I could think of.)
I would use a pair of plugins to get the job done. Which would use four models. Snowboard, Store, User and Audit.
acts_as_state_machine and acts_as_audited
AASM simplifies the state transitions. While auditing creates the history you want.
The code for Store and User is trivial and acts_as_audited will handle the audits model.
class Snowboard < ActiveRecord::Base
include AASM
belongs_to :store
aasm_initial_state :unread
acts_as_audited :only => :state
aasm_state :maintenance
aasm_state :available
aasm_state :rented
aasm_event :send_for_repairs do
transitions :to => :maintenance, :from => [:available]
end
aasm_event :return_from_repairs do
transitions :to => :available, :from => [:maintenance]
end
aasm_event :rent_to_customer do
transitions :to => :rented, :from => [:available]
end
aasm_event :returned_by_customer do
transitions :to => :available, :from => [:rented]
end
end
class User < ActiveRecord::Base
has_many :full_history, :class_name => 'Audit', :as => :user,
:conditions => {:auditable_type => "Snowboard"}
end
Assuming your customer is the current_user during the controller action when state changes that's all you need.
To get a snowboard history:
#snowboard.audits
To get a customer's rental history:
#customer.full_history
You might want to create a helper method to shape a customer's history into something more useful. Maybe something like his:
def rental_history
history = []
outstanding_rentals = {}
full_history.each do |item|
id = item.auditable_id
if rented_at = outstanding_rentals.keys.delete(id)
history << {
:snowboard_id => id,
:rental_start => rented_at,
:rental_end => item.created_at
}
else
outstanding_rentals[:id] = item.created_at
end
end
history << oustanding_rentals.collect{|key, value| {:snowboard_id => key,
:rental_start => value}
end
end
First I would generate separate models for Snowboard, Customer and Store.
script/generate model Snowboard name:string price:integer ...
script/generate model Customer name:string ...
script/generate model Store name:string ...
(rails automatically generates id and created_at, modified_at dates)
To preserve the history, I wouldn't copy rows/values from those tables, unless it is necessary (for example if you'd like to track the price customer rented it).
Instead, I would create SnowboardEvent model (you could call it SnowboardHistory if you like, but personally it feels strange to make new history) with the similiar properties you described:
ev_type (ie. 0 for RETURN, 1 for MAINTENANCE, 2 for RENT...)
snowboard_id (not null)
customer_id
store_id
For example,
script/generate model SnowboardEvent ev_type:integer snowboard_id:integer \
customer_id:integer store_id:integer
Then I'd set all the relations between SnowboardEvent, Snowboard, Customer and Store. Snowboard could have functions like current_state, current_store implemented as
class Snowboard < ActiveRecord::Base
has_many :snowboard_events
validates_presence_of :name
def initialize(store)
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:store_id => store.id,
:snowboard_id = id,
:customer_id => nil})
ev.save
end
def current_state
ev = snowboard_events.last
ev.ev_type
end
def current_store
ev = snowboard_events.last
if ev.ev_type == RETURN
return ev.store_id
end
nil
end
def rent(customer)
last = snowboard_events.last
if last.ev_type == RETURN
ev = SnowboardEvent.new(
{:ev_type => RENT,
:snowboard_id => id,
:customer_id => customer.id
:store_id => nil })
ev.save
end
end
def return_to(store)
last = snowboard_events.last
if last.ev_type != RETURN
# Force customer to be same as last one
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:snowboard_id => id,
:customer_id => last.customer.id
:store_id => store.id})
ev.save
end
end
end
And Customer would have same has_many :snowboard_events.
Checking the snowboard or customer history, would be just a matter of looping through the records with Snowboard.snowboard_events or Customer.snowboard_events. The "temporal data" would be the created_at property of those events. I don't think using Observer is necessary or related.
NOTE: the above code is not tested and by no means perfect, but just to get the idea :)

Resources