Find Or Create Rails Nested Attribute By Name - ruby-on-rails

I have a Model called Example that accepts_nested_attributes_for NestedExample.
When I create a new Example Model I can also create NestedExamples:
params = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
Example.find_or_create_by_name params
This is all working fine. However, I rather than creating a new NestedExample every time, I would like Rails to perform a find_or_create_by_name on the NestedExample model, so that in the above situation, if there is already a NestedModel with a name of Nested Example 1, it will be used, rather than a new instance of NestedExample with the same name.
My current result:
params_1 = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
params_2 = { :name => 'Example 2', :nested_example_attributes => { :name => 'Nested Example 1' } }
example_1 = Example.find_or_create_by_name params_1
example_2 = Example.find_or_create_by_name params_2
puts example_1.nested_example.id == example_2.nested_example.id # Should be true

Related

How to merge two ActiveRecord queries into one hash and convert to JSON?

I'm using AJAX to get some results, but the problem is I'm querying two separate models and I want to return both and their relationship to one another as one JSON object.
Here's an example of two models I'm trying to link together -
Car
belongs_to :user
:id
:make
:year
:user_id
User
has_many :cars
:id
:first_name
:last_name
:birthday
I'm trying to get it to look something like this -
{
1: {
id: 1,
first_name: 'Joe'
last_name: 'Smith'
cars: {
23: {
id: 23,
make: 'BMW',
year: 2009,
user_id: 1
},
24: {
id: 24,
make: 'Volvo',
year: 2012,
user_id: 1
}
}
},
2: {
id: 2,
first_name: 'Bob'
last_name: 'Johnson'
cars: {
35: {
id: 35,
make: 'Ford',
year: 2013,
user_id: 2
}
}
}
}
Create a new (private) method in your controller:
def format_json(users)
result = {}
users.each do |user|
result[user.id] = user.formatted_data
end
return result
end
Change the action to return:
users = Users.includes(:cars).where("<your_where_clause>").limit(<n>)
render :json => { :result => format_json(users) }.to_json
app/models/user.rb
class User < ActiveRecord::Base
def formatted_data
{
:id => self.id,
:first_name => self.first_name,
:last_name => self.last_name,
:cars => self.get_car_info
}
end
def get_car_info
car_info = {}
self.cars.each do |car|
car_info[car.id] = car.info
end
return car_info
end
end
app/models/car.rb
class Car < ActiveRecord::Base
def info
{
:id => self.id,
:make => self.make,
:year => self.year,
:user_id => self.user_id
}
end
end
I ended up using the .as_json I found detailed here to convert ActiveRecord to a plain Ruby hash and passed that hash to the view.
user = User.find(params[:user_id].to_i)
#json = {}
#json[user.id] = user.as_json
#json[user.id]['cars'] = {}
user.cars.each do |car|
#json[user.id]['cars'][car.id] = car.as_json
end
render json: { status: true, users: #json }

create a specific hash (dynamic)

How I can create a hash like this in a cycle ?
User.items.each do |m|
......
Result:
test = [{:name => 'Unit 1', :price => "10.00"},
{:name => 'Unit 2', :price => "12.00"},
{:name => 'Unit 3', :price => "14.00"}]]
You can use map to return hashes that you build.
Assuming your Item resource responds to name and price, it would look like
test = User.items.map do |m|
{
name: m.name,
price: m.price
}
end
You also can do like this:
Item.connection.select_all("select name, price from items where user_id = xxxxx;")
you will get an array containing hash, like this:
[{"name"=>"xxx", "price"=> xxx},{}......]

Where to put constants in Rails

I have a few constants which are arrays that I don't want to create databse records for but I don't know where to store the constants without getting errors.
For example
CONTAINER_SIZES = [["20 foot"],["40 foot"]]
Where can I store this so all models and controller have access to this?
I will write my way to you.
class User < ActiveRecord::Base
STATES = {
:active => {:id => 100, :name => "active", :label => "Active User"},
:passive => {:id => 110, :name => "passive", :label => "Passive User"},
:deleted => {:id => 120, :name => "deleted", :label => "Deleted User"}
}
# and methods for calling states of user
def self.find_state(value)
if value.class == Fixnum
Post::STATES.collect { |key, state|
return state if state.inspect.index(value.to_s)
}
elsif value.class == Symbol
Post::STATES[value]
end
end
end
so i can call it like
User.find_state(:active)[:id]
or
User.find_state(#user.state_id)[:label]
Also if i want to load all states to a select box and if i don't want some states in it (like deleted state)
def self.states(arg = nil)
states = Post::STATES
states.delete(:deleted)
states.collect { |key, state|
if arg.nil?
state
else
state[arg]
end
}
end
And i can use it now like
select_tag 'state_id', User.states.collect { |s| [s[:label], s[:id]] }
I put them directly in the model class.
class User < ActiveRecord::Base
USER_STATUS_ACTIVE = "ACT"
USER_TYPES = ["MANAGER","DEVELOPER"]
end

Nested forms and automatic creation of parent, children

I was wondering if it was possible to create new parent, children in a has many relationship, using rails nested forms.
Rails documentation clearly says that this works in a one to one relationship. Not sure if its the same in has many relationship.
For example:
If
params = {
:employee => {
:name => "Tester",
:account_attributes => {:login => 'tester'}
}
}
works as one to one relationship. So Employee.new(params) works fine. New employee, account are created.
Supposing I had
params = {
:employee => {
:name => "Tester",
:account_attributes => {
"0" => {:login => 'tester'},
"1" => {:login => 'tester2'}
}
}
}
Employee.new(params) doesnt work. It fails on child validations saying parent cant be blank.
Any help is appreciated. Thanks
Karen
The child_attributes= writer that comes with accepts_nested_attributes_for expects an array when it comes to one to many relationships.
This will create two accounts for the new employee
params = {
:employee => {
:name => "Tester",
:account_attributes => [
{:login => 'tester'},
{:login => 'tester2'}
]
}
}

Retrieve all association's attributes of an AR model?

What do you think is the most optimal way to retrieve all attributes for all the associations an AR model has?
i.e: let's say we have the model Target.
class Target < ActiveRecord::Base
has_many :countries
has_many :cities
has_many :towns
has_many :colleges
has_many :tags
accepts_nested_attributes_for :countries, :cities, ...
end
I'd like to retrieve all the association's attributes by calling a method on a Target instance:
target.associations_attributes
>> { :countries => { "1" => { :name => "United States", :code => "US", :id => 1 },
"2" => { :name => "Canada", :code => "CA", :id => 2 } },
:cities => { "1" => { :name => "New York", :region_id => 1, :id => 1 } },
:regions => { ... },
:colleges => { ... }, ....
}
Currently I make this work by iterating on each association, and then on each model of the association, But it's kind of expensive, How do you think I can optimize this?
Just a note: I realized you can't call target.countries_attributes on has_many associations with nested_attributes, one_to_one associations allow to call target.country_attributes
I'm not clear on what you mean with iterating on all associations. Are you already using reflections?
Still curious if there's a neater way, but this is what I could come up with, which more or less results in the hash you're showing in your example:
class Target < ActiveRecord::Base
has_many :tags
def associations_attributes
# Get a list of symbols of the association names in this class
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
# Fetch myself again, but include all associations
me = self.class.find self.id, :include => association_names
# Collect an array of pairs, which we can use to build the hash we want
pairs = association_names.collect do |association_name|
# Get the association object(s)
object_or_array = me.send(association_name)
# Build the single pair for this association
if object_or_array.is_a? Array
# If this is a has_many or the like, use the same array-of-pairs trick
# to build a hash of "id => attributes"
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
# has_one, belongs_to, etc.
[association_name, object_or_array.attributes]
end
end
# Build the final hash
Hash[*pairs.flatten(1)]
end
end
And here's an irb session through script/console to show how it works. First, some environment:
>> t = Target.create! :name => 'foobar'
=> #<Target id: 1, name: "foobar">
>> t.tags.create! :name => 'blueish'
=> #<Tag id: 1, name: "blueish", target_id: 1>
>> t.tags.create! :name => 'friendly'
=> #<Tag id: 2, name: "friendly", target_id: 1>
>> t.tags
=> [#<Tag id: 1, name: "blueish", target_id: 1>, #<Tag id: 2, name: "friendly", target_id: 1>]
And here's the output from the new method:
>> t.associations_attributes
=> {:tags=>{1=>{"id"=>1, "name"=>"blueish", "target_id"=>1}, 2=>{"id"=>2, "name"=>"friendly", "target_id"=>1}}}
try this with exception handling:
class Target < ActiveRecord::Base
def associations_attributes
tmp = {}
self.class.reflections.symbolize_keys.keys.each do |key|
begin
data = self.send(key) || {}
if data.is_a?(ActiveRecord::Base)
tmp[key] = data.attributes.symbolize_keys!
else
mapped_data = data.map { |item| item.attributes.symbolize_keys! }
tmp[key] = mapped_data.each_with_index.to_h.invert
end
rescue Exception => e
tmp[key] = e.message
end
end
tmp
end
end
This is updated version of Stéphan Kochen's code for Rails 4.2
def associations_attributes
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
me = self.class.includes(association_names).find self.id
pairs = association_names.collect do |association_name|
object_or_array = me.send(association_name)
if object_or_array.is_a? ActiveRecord::Associations::CollectionProxy
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
[association_name, object_or_array.attributes]
end
end
Hash[*pairs.flatten(1)]
end

Resources