Ruby on Rails multiple belongs_to associations saving - ruby-on-rails

There is something i don't quite understand in Rails's belongs_to concept. Documentation states:
Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object
Let's say i have an Employee entity:
class Employee < ActiveRecord::Base
belongs_to :department
belongs_to :city
belongs_to :pay_grade
end
Will the following code fire three updates and if so is there a better way to do it? :
e = Employee.create("John Smith")
Department.find(1) << e
City.find(42) << e
Pay_Grade.find("P-78") << e

You can simply assign it:
e = Employee.new(:name => "John Smith")
e.department = Department.find(1)
e.city = City.find(42)
e.pay_grade = Pay_Grade.where(:name => "P-78")
e.save
I changed the create to new to construct the object before saving it. The constructor takes a hash, not different values. find takes only the id and not a string, use where on a field instead.
You can also use the following:
Employee.create(:name => "John Smith",
:department => Department.find(1),
:city => City.find(42),
:pay_grade => PayGrade.where(:name => "P-78").first
Also note that model names should be camel case: PayGrade instead of Pay_Grade.

Related

find a user by model associations rails

I have a model Camera in which
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
i have asscociation like this. when i do Camera.first
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
i can get owner id, is there any way to create such function that , along side getting owner id, i can get the data which linked with this id for example at id = 4
#<EvercamUser id: 4, created_at: "2013-12-12 16:43:46", updated_at: "2015-04-16 15:23:19", firstname: "Garrett", lastname: "Heaver", username: "garrettheaver"
this user is present, what if when i do Camera.first then instead of OnwerID, how can i get the owners Name?
Any help will be appreciated!
how can i get the owners Name
You'd call the associative object on the Camera object:
#camera = Camera.find x
#user = #camera.user
#user.name #-> outputs name of associated user object
... this will allow you to call the attributes of the child object on it: #camera.user.name or #camera.user.email, etc
Off topic, but I always include a reference to delegate for this type of issue; it avoids the law of demeter (where you're using more than one point to access data).
This would allow you to use:
#app/models/camera.rb
class Camera < ActiveRecord::Base
belongs_to :user, foreign_key: :owner_id, class_name: 'EvercamUser'
delegate :name, to: :user, prefix: true #-> #camera.user_name
end
#camera = Camera.find x
#camera.user_name #-> outputs the user's name on the camera object (not user object)
To give you some context, Rails uses ActiveRecord to invoke/create objects for you.
In line with the object orientated nature of Rails, ActiveRecord is known as an ORM (Object Relationship Mapper). This basically allows you to create an object through ActiveRecord, and if it is associated to another (as Rails does with its associations), it will append the associated object onto the parent.
Thus, when you're asking about calling owner_id, you're referring to the foreign_key of the association (the database column which joins the two tables together):
What you need is to reference the associated object, which I've detailed above.
What about using join here?
Camera.all.joins(:evercamusers)
Camera.where(:id => 1).joins(:users).first
Note: I'm a bit unsure if the correct parameter should be ":users" or ":evercamusers"
http://apidock.com/rails/ActiveRecord/QueryMethods/joins
You could also add methods to your class to do this.
class Camera < ActiveRecord::Base
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
def firstname
self.user.firstname
end
end
When you try to output data from Camera like this:
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
It won't show. But if you call the method like this, it should work:
Camera.first.firstname # "Garrett"
Also, if JSON is acceptable you could override the as_json method.
def as_json(options={})
{ :firstname => self.user.firstname }
end
Then call it with
Camera.first.as_json
If you need to do it with all, simply loop it
Camera.all.each { |c| puts c.firstname }

add validations to inherited model in rails 4

The code is simply inherited model.
But unfortunately validation does not work. The idea is to select fathers from parrots. When I loop #fathers - it shows all parrots but should select only those who have age more than 12 and so on. Or maybe I do something wrong?
Model
class Father < Parrot
has_many :parrots
validates :age, :numericality => { :greater_than => 12}
validates :tribal, :acceptance => true
validates_inclusion_of :sex, :in => %w( Male )
end
view
<% #fathers.each do || %>
<%= f.name %>
<% end %>
controller
def index
#parrots = Parrot.all
#fathers = Father.all
end
The validation criteria has nothing to do with how data is queried, just that it passes the defined criteria before object is written to the database. Are you saying that Father object doesn't perform the validation and persists invalid data?
Are you sure that all of the father parrots are being saved through the Father object? You should also have a type column in your parrots column that has either 'Parrot' or 'Father' value. When you execute Father.all it should be running a query that looks like this:
SELECT * FROM parrots WHERE type='Father';
filtering out parrots that were not saved through the Father object.
If you just need to pull Parrots that match Father criteria from the DB you can use scopes:
class Father < ActiveRecord::Base
self.table_name = 'parrots'
default_scope { where("age > 12 and tribal = 'true' and sex='Male'")}
#whatever else
end
Here's additional information on Single Table Inheritance and scopes
Validations are used when the Father is saved. The "type" column in the db will determine the class. Be sure that is set correctly.
But, is Father really a separate class? Or just a Parrot with certain attributes. It seems that Parrot.all would include all Fathers.
It may be a scope.
class Parrot < ActiveRecord::Base
scope :fathers, -> { where(:sex => 'Male').where('age >= 12').where(:tribal => true) }
end
Then you can get fathers by
#fathers = Parrot.fathers

Adjacency data structure to nested hash

I have the following model in rails:
class Model < ActiveRecord::Base
# id — integer
# name — string
# model_id — integer
belongs_to :parent, class_name: 'Model', foreign_key: 'model_id'
has_many :children, class_name: 'Model', foreign_key: 'model_id'
end
I am using adjacency structure, which can have infinite depth. I am on a Postgres database using recursive selects.
What will be the most sane way to get a nested hash of objects? I tried to select instances of Model and sort them, yet could not bring this to any usable result.
Lets say I have four Model instances saved in my database: Model_1, Model_2, Model_3 and Model_4. Model_3 is a child of Model_2 and Model_4 is a child of Model_3.
Here is an output I am trying to achieve (a nested hash of Model instances):
{
#<Model_1...> => {},
#<Model_2...> => {
#<Model_3...> => {
#<Model_4...> => {}
}
}
}
Any ideas?
Update: Tree is already recovered — either as a CollectionProxy, Relation or any other array-ish data structure. I wan't to sort that tree into the hash of nested hashes.
I would name it parent_id field.
belongs_to :parent, class_name: "Model"
has_many :children, class_name: "Model", foreign_key: "parent_id"
When you have the hash, you would use sort or sort_by:
http://www.ruby-doc.org/core-2.1.0/Enumerable.html#method-i-sort_by
def sort(hash)
hash.sort { |m1, m2| m1.id <=> m2.id }
sort(hash.children)
end
First, define the ff method inside your Model class:
def to_hash
if children.empty?
{self => {}}
else
{self => children.inject({}) {|hash, model| hash.merge(model.to_hash) } }
end
end
Then do the ff to get the output you want:
top_level_models = #code that queries top-level models while eager-loading all nested children
hash_of_nested_models = top_level_models.inject({}) {|hash, ancestor| hash.merge(ancestor.to_hash) }
The hash argument that you pass to includes should cover the depth of your nesting. The argument passed to includes above will be the nesting for children with a depth of 3 descendants. For as long as you includes all the nested children in your where query, generating the hash will not do any more db queries.
Hope that helps!
Getting AR to do this without N+1 queries would be difficult. Will have to write something that works with the data in memory.
You would have to write a custom function which looks something like:
def to_hash
root_hash = {}
# Load all the model into memory and into a hash to allow faster access when we have the id
models = Hash[Model.all.collect {|m| [m.id, m]}]
# The resultant hash for each child
models_with_hash = Hash[map.values.collect {|m| [m.id, {}]} ]
# Stitch them together
models.each do |id, m|
if m.model_id.nil?
root_hash.merge! m => models_with_hash[m.id]
else
# should name model_id to parent_id
models_with_hash[m.model_id].merge! m => models_with_hash[m.id]
end
end
root_hash
end

Creating multiple habtm relationships with two models in one request

I currently have an Album/Artist database that uses multiple join tables to signify when how an artist relates to the album. They can be listed as a Composer, an Arranger, or a Performer. That is:
class Artist < ActiveRecord::Base
...
has_and_belongs_to_many :albumbycomposers, :class_name => "Album", :join_table => "albums_composers"
has_and_belongs_to_many :albumbyarrangers, :class_name => "Album", :join_table => "albums_arrangers"
has_and_belongs_to_many :albumbyperformers, :class_name => "Album", :join_table => "albums_performers"
...
end
And
class Album < ActiveRecord::Base
has_and_belongs_to_many :composers, :class_name => "Artist", :join_table => "albums_composers"
has_and_belongs_to_many :arrangers, :class_name => "Artist", :join_table => "albums_arrangers"
has_and_belongs_to_many :performers, :class_name => "Artist", :join_table => "albums_performers"
...
end
This code is used to look up for existing artists in the database, then create the association. If no artist exists, then I use the .build method to create the artist.
class AlbumsController < ApplicationController
...
def create
#album = Album.new(params[:album])
params["Composer Names"].each do |each|
if each.empty? == false
#exists = Artist.find_by_name(each)
if #exists.nil? == true
#album.composers.build(:name => each)
else
#album.composers << Artist.find_by_name(each)
end
end
end
params["Arranger Names"].each do |each|
if each.empty? == false
#exists = Artist.find_by_name(each)
if #exists.nil? == true
#album.arrangers.build(:name => each)
else
#album.arrangers << Artist.find_by_name(each)
end
end
end
...
end
...
end
The problem I encounter occurs when I try to enter a new artist as both a composer and an arranger. For example, say I submit this as a post request
Parameters: {"Name"=>["New Album"],
"Performer Names"=>["New Artist"],
"Composer Names"=>["New Artist"],
"Arranger Names"=>["New Artist"],
...
}
Since the composer arguments are first, rails interprets them properly (as if the artist does not exist). The arranger and performer arguments are also interpreted as if the artist does not exist. Then rails begins inserting data into my database. First the album is created and inserted into the albums table, then "New Artist" is created and inserted into album_composer (according to the .build method).
However, for the arranger and performer arguments, the build method can no longer be used, since the artist has been created, so the code is not executed properly.
I tried to workaround by using the push method (aka <<) in the arranger and performer argument lines for this specific case, but that doesn't work because it instantly fires without waiting for the artist to be made by the composer argument, resulting in an "Artist cannot be found" error. For Reference:
collection<<(object, …)
Adds one or more objects to the collection by creating associations in the join table (collection.push and collection.concat are aliases to this method).
Note that this operation instantly fires update sql without waiting for the save or update call on the parent object.
What is the proper way to handle this?
I solved the problem by adding in each artist separately into my database first using the code:
#artists = params["Composer Names"] | params["Arranger Names"] | params["Performer Names"]
#artists.each do |artist|
if artist.empty? == false
#exists = Artist.find_by_name(artist)
if #exists.nil?
#artist = Artist.new(:name => artist)
#artist.save
end
end
end
I could use the push method and add each artist to the proper join table using code like:
params["Composer Names"].each do |each|
if each.empty? == false
#album.composers << Artist.find_by_name(each)
end
end
so that's that!

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