Use hash to build ApplicationRecord - ruby-on-rails

Is there a function that allows me to add a hash of data to an initialized ApplicationRecord such that the following would work?
$ foo = Foo.new(bar: 4)
class Foo < ApplicationRecord {
:id => nil,
:bar => 4,
:x => nil,
:y => nil
}
$ data = {x: 5, y: 6}
$ foo.INSERT_METHOD_HERE(data)
class Foo < ApplicationRecord {
:id => nil,
:bar => 4,
:x => 5,
:y => 6
}
I thought such a method exists but I cannot recall the name

Related

how to use an instance of a model in another serializer

I'm stuck here and couldn't find solution to proceed my work,
I have 3 models: plans, days, and meals.
This is my Plan Controller I've managed to get the correct answer in the controller, I want it nested and inside the serializer because I'm using URL helper to retrieve my images URLs, is there a possible way to use the #plan.id inside the DaySerializer?
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => { :plan => #plan, :days => #days,
:meals => #meals }
end
This is my Plan model
class Plan < ApplicationRecord
has_many :days
has_one_attached :image, dependent: :destroy
end
This is my Day model
class Day < ApplicationRecord
has_many :meals
has_many :plans
end
This is my Meal model
class Meal < ApplicationRecord
belongs_to :plan
belongs_to :day
has_one_attached :image, dependent: :destroy
end
I want to show all meals for a specific Plan, to do that I need to use a variable inside the daySerializer but I couldn't find how to do it.
This is my planSerializer
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
def plan_days
object.days.map do |day|
DaySerializer.new(day, scope: scope, root: false, event: object)
end
end
end
and this is my DaySerializer which I need to use the instance of the plan inside
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
#how to be able to use this line in Serilizer? !important
#plan = Plan.find(params[:id])
object.meals.map do |meal|
if meal.plan_id == #plan.id
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
target reason response :
{
id: 8,
name: "Plan1",
monthly_price: 88,
plan_days: [
{
number: 5,
plan_meals: [],
},
{
number: 4,
plan_meals: [],
},
{
number: 3,
plan_meals: [],
},
{
number: 2,
plan_meals: [],
},
{
number: 1,
plan_meals: [
{
id: 11,
name: "test meal",
calories: 32,
protein: 32,
fat: 32,
carbohydrates: 32,
plan_id: 8,
},
],
},
],
}
currently it's showing all meals that belongs to each day,
not only the meals with the plan_id = Plan.find(params[:id])
In general I think you could use something like this should work.
ActiveModel::Serializer::CollectionSerializer.new. It actually by itself allows you to pass additional information to your serializer. It does the same as your current code just you are able to explicitly pass new data.
Controller:
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => {
:plan => #plan,
:days => ActiveModel::Serializer::CollectionSerializer.new(#days, serializer: DaySerializer, plan_id: #plan.id),
:meals => #meals
}
end
And then in DaySerializer:
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
object.meals.map do |meal|
if meal.plan_id == instance_options[:plan_id]
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
So in short ActiveModel::Serializer::CollectionSerializer.new in controller and instance_options in serializer to access passed additional parameters.
UPDATED:
How about add meal serializer?
class MealSerializer < ActiveModel::Serializer
attributes :id, :name, :calories, :protein, :fat, # etc
end
class DaySerializer < ActiveModel::Serializer
attributes :number
has_many :meals, serializer: MealSerializer
end
ORIGINAL:
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
has_many :plan_days, serializer: DaySerializer
end
something like this.

How to get enum value from its index in ruby-on-rails?

For example I have this enum in ruby-on-rails:
class Foo < ActiveRecord::Base
enum color: [ :red, :green, :blue ]
end
By default, the index should be :red -> 0, :green -> 1, and so on.
I want to get the enum value by index, let's say from index 1, so the result should be :green. Is it possible to do this ?
Update:
Pseudo-code example:
Foo.colors.find_by_index(1) # returns :green
Foo.colors returns a hash:
{:red => 0, :green => 1, :blue => 2}
You can invert the hash to get the indexed value
Foo.colors.invert
# {0 => :red, 1 => :green, 2 => :blue}
You can try these
class Fooo < ActiveRecord::Base
enum colo: {red: 0, green: 1, blue: 2}
end

Rails - Determine what properties on an object are set by a setter

Given this class:
class MyModel < ActiveRecord::Base
belongs_to :association1
belongs_to :association2, :polymorphic => true
end
I know that when I set association1, it sets association1_id to the ID of object 1
m = MyModel.new
m.association1 = object1
#<MyModel id: nil, association1_id: 1, association2_id: nil, association2_type: nil>
I know that when I set association2, it sets association2_id AND association2_type
m.association2 = object2
#<MyModel id: nil, association1_id: 1, association2_id: 2, association2_type: 'ClassType'>
My question is:
Is there a function that can easily tell me what information is being set on an object in hash form?
MyModel.magic_function(:association1, object1)
# returns {:association1_id => 1}
MyModel.magic_function(:association2, object2)
# returns {:association2_id => 2, :association2_type => 'ClassType'}
Perhaps you're looking for changes:
person = Person.new
person.changes # => {}
person.name = 'bob'
person.changes # => { 'name' => [nil, 'bob'] }
This is the stop gap solution I have for now, just though I'd share:
def self.magic_method(association, object)
instance = self.new
instance.send(association, object)
h = Hash.new
instance.changes.each do |k,v|
h[k] = v[1]
end
h
end
Is this built into rails somewhere?

ActiveRecord poly Appended Array vs Concatenated Array

Why does the connections table get updated when I call #user.connections for the following?
Connection Model
class Connection < ActiveRecord::Base
belongs_to :left_nodeable, :polymorphic => true
belongs_to :right_nodeable, :polymorphic => true
# Statuses:
PENDING = 0
ACCEPTED = 1
named_scope :pending, :conditions => { :connection_status => PENDING }
named_scope :accepted, :conditions => { :connection_status => ACCEPTED }
end
User Model
class User < ActiveRecord::Base
has_many :left_connections, :as => :left_nodeable, :class_name => 'Connection', :conditions => {:left_nodeable_type => 'User', :right_nodeable_type => 'User'}
has_many :right_connections, :as => :right_nodeable, :class_name => 'Connection', :conditions => {:right_nodeable_type => 'User', :left_nodeable_type => 'User'}
def connections
self.left_connections << self.right_connections
end
end
If I use:
def connections
self.left_connections + self.right_connections
end
Then the model works ok but I cannot use any of my named_scope methods.
So I guess my questions boils down to...
What is the difference between the "<<" and "+" operator on an ActiveRecord? Why does using "<<" change the database, and using "+" cause named_scope methods to fail?
The model is updated because left_connections is updated with the << method. This makes left_connections = left_connections + right_connections.
arr = [1,2]
arr << [3,4]
arr #=> [1,2,3,4]
-------------------------
arr = [1,2]
arr + [3,4] #=> [1,2,3,4]
arr #=> [1,2]
self.left_connections + self.right_connections is the correct way to return a concatenation. As for your named_scope methods, I couldn't tell you why they're failing without seeing them.

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