Rails belongs_to and single table inheritance not behaving - ruby-on-rails

I have a Bike model and a Component model. Several models inherit from Component: Frame, Chain, Crankset etc.
When I submit my form, my params look like this:
"bike" => { "frame" => { "id" => "4" }, "chain" => { "id" => "19" }, ... }
In my controller, the following code breaks:
#bike = Bike.new(params[:bike])
> Frame(#90986230) expected, got HashWithIndifferentAccess(#81888970)
If I hack my form to generate the following params, it works:
"bike" => { "frame_id" => "4", "chain_id" => "19" ... }
Here's my models:
class Bike < ActiveRecord::Base
belongs_to :frame
belongs_to :chain
...
end
class Component < ActiveRecord::Base
has_many :bikes
end
class Frame < Component
end
Single table inheritance is working - I can call Frame.first and Component.all without issue.
Am I going insane? Isn't the nested params the usual convention? That's what gets generated by:
- f.fields_for #bike.frame do |frame|
= frame.hidden_field :id
What am I doing wrong??

You are using nested forms, so nested params should work if you use the accepts_nested_attributes_for tag (see railscast 196/197).
belongs_to :frame
accepts_nested_attributes_for :frame

Related

POSTing JSON with Association data

I am trying to send a POST request to a rails scaffold controller which contains a nested array representing records that need to be created and associated with the newly created parent.
Here is some example JSON:
{
"plan_id":3,
"weight":60,
"exercise_sets": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
..and my models..
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
end
class ExerciseSet < ActiveRecord::Base
belongs_to :exercise
belongs_to :session
end
Is what I am attempting possible?
This is certainly not impossible, though you may have to switch up your parameter naming a bit.
When you pass the JSON above to the controller, it either gets passed as parameters to a constructor:
Session.new(params[:session])
Or gets passed to the #update_attributes method on a persisted Session instance:
#session = Session.find(params[:id])
#session.update_attributes(params[:session])
Both the constructor and #update_attributes methods turn parameters like "plan_id" into assigment method calls. That is,
#session.update_attributes(:plan_id => "1")
Turns into (inside the #update_attributes method):
#session.plan_id = "1"
So, this works for your plan_id and weight attributes, because you have both #plan_id= and #weight= setter methods. You also have an #exercise_sets= method given to you by has_many :exercise_sets. However, the #exercise_sets= method expects ExerciseSet objects, not ExerciseSet attributes.
Rails is capable of doing what you are trying to do via the #accepts_nested_attributes_for class method. Try this:
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
accepts_nested_attributes_for :exercise_sets
end
This sets up (metaprograms) an #exercise_sets_attributes= method for you. So just modify your JSON to:
{
"plan_id":3,
"weight":60,
"exercise_sets_attributes": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
More info: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It's perfectly possible, you just have to add the following line to your Session model:
accepts_nested_attributes_for :exercise_sets, :reject_if => lambda { |a| a[:exercise_id].blank? }, :allow_destroy => true
Now, when your controller does Session.new(params[:session]), Rails will build (or update) the Session and the related ExerciceSet(s).
Please review the :reject_if clause. That's where you define which records will be created and which will not.
This are the basics, but Ryan Bates explains the nested model forms perfectly (as always) in this screencast.

Is there any rails syntactic sugar that could make this prettier?

I've written a model for "Category". The requirements here are that each category can fall into one category "type". I'm learning rails at the same time as doing this project, and managed to get the above working with the following class method (where_category_type);
class Category < ActiveRecord::Base
#associations
belongs_to :category_type
has_and_belongs_to_many :recipes
def self.where_category_type category_type
Category.find(:all, :include => :category_type, :conditions => { :category_types => {:name => category_type }})
end
end
All works etc. but I am very keen to make sure I'm doing things "the rails way", so I was wondering if I'm missing some syntactic sugar somewhere that would make this a little more readable / less verbose?
class Category < ActiveRecord::Base
#associations
belongs_to :category_type
has_and_belongs_to_many :recipes
end
Then instead of defining *where_category_type* Category's class static method, you can call just:
Category.joins(:category_type).where('category_types.name' => 'name of your category').all

ActiveRecord query, joins (Rails 3.1)

How would I go about getting a JSON object with
[
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
},
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
},
...,
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
}
]
sorted by some key (can be a field in either Property or PropAssignmConsumer) for a given user_entity object? i.e. get all properties linked to a given consumer/user_entity, extracting fields from both properties and prop_assignm_consumers, sorting by a field in the properties or prop_assignm_consumer table.
These are my models:
class Property < ActiveRecord::Base
has_many :prop_assignm_consumers, :dependent => :restrict
end
class PropAssignmConsumer < ActiveRecord::Base
belongs_to :consumer
belongs_to :property
end
class Consumer < UserEntity
has_many :prop_assignm_consumers, :dependent => :destroy
has_many :properties, :through => :prop_assignm_consumers
end
I am currently doing
properties = user_entity.properties.find(:all, :order => "#{sort_key} #{sort_ord}")
properties.each do |p|
a = p.prop_assignm_consumers.find_by_consumer_id(current_user.user_entity.id)
... do something with a and p....
end
but this seems inefficient....
Any help would be appreciated.
Maybe I'm missing something. Why doesn't your property also reference consumers? You have many to many, you just didn't complete it. Just adding has_many :consumers, :through => :prop_assignm_consumer would then let you do
properties = user_entity_properties.all(:include => :consumers)
properties.each do |p|
p.consumers.where(:id => current_user.user_entity.id)
end
Though now that we write that, and given that you're doing find_by and not find_all_by, it's pretty clear there's going to be only 1. So you can go the other way.
consumer = Consumer.where(:id => current_user.user_entity.id).includes(:properties).first
consumer.properties.each do |p|
... do something with p and consumer
end
ref
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Remove assosiation instead of destroying object when :allow_destroy => true

When using the new accepts_nested_attributes_for in ActiveRecord, it's possible to use the option :allow_destroy => true. When this option is set, any hash containing nested attributes like {"_delete"=>"1", "id"=>"..."} passed to update_attributes will delete the nested object.
Simple setup:
class Forum < ActiveRecord::Base
has_many :users
accepts_nested_attributes_for :users, :allow_destroy => true
end
class User < ActiveRecord::Base
belongs_to :forum
end
Forum.first.update_attributes("users_attributes"=>{"0"=>{"_delete"=>"1", "id"=>"42"}})
Question: How do I - instead of deleting the nested objects when "_delete" => "1" - just remove the association? (i.e. In the above case set the forum_id on the user to nil)
Bonus question: What if I also want to change the an attribute on the nested object when removing the association? (e.g. like setting a state or a timestamp)
Instead of asking for the user to be deleted using "_delete" => '1', can you not just update it using the nested_attributes?:
Forum.first.update_attributes("users_attributes"=> {
"0" => {
"id" => "42",
"forum_id" => "",
"state" => 'removed'
}
})

Rails nested form with has_many :through, how to edit attributes of join model?

How do you edit the attributes of a join model when using accepts_nested_attributes_for?
I have 3 models: Topics and Articles joined by Linkers
class Topic < ActiveRecord::Base
has_many :linkers
has_many :articles, :through => :linkers, :foreign_key => :article_id
accepts_nested_attributes_for :articles
end
class Article < ActiveRecord::Base
has_many :linkers
has_many :topics, :through => :linkers, :foreign_key => :topic_id
end
class Linker < ActiveRecord::Base
#this is the join model, has extra attributes like "relevance"
belongs_to :topic
belongs_to :article
end
So when I build the article in the "new" action of the topics controller...
#topic.articles.build
...and make the nested form in topics/new.html.erb...
<% form_for(#topic) do |topic_form| %>
...fields...
<% topic_form.fields_for :articles do |article_form| %>
...fields...
...Rails automatically creates the linker, which is great.
Now for my question: My Linker model also has attributes that I want to be able to change via the "new topic" form. But the linker that Rails automatically creates has nil values for all its attributes except topic_id and article_id. How can I put fields for those other linker attributes into the "new topic" form so they don't come out nil?
Figured out the answer. The trick was:
#topic.linkers.build.build_article
That builds the linkers, then builds the article for each linker. So, in the models:
topic.rb needs accepts_nested_attributes_for :linkers
linker.rb needs accepts_nested_attributes_for :article
Then in the form:
<%= form_for(#topic) do |topic_form| %>
...fields...
<%= topic_form.fields_for :linkers do |linker_form| %>
...linker fields...
<%= linker_form.fields_for :article do |article_form| %>
...article fields...
When the form generated by Rails is submitted to the Rails controller#action, the params will have a structure similar to this (some made up attributes added):
params = {
"topic" => {
"name" => "Ruby on Rails' Nested Attributes",
"linkers_attributes" => {
"0" => {
"is_active" => false,
"article_attributes" => {
"title" => "Deeply Nested Attributes",
"description" => "How Ruby on Rails implements nested attributes."
}
}
}
}
}
Notice how linkers_attributes is actually a zero-indexed Hash with String keys, and not an Array? Well, this is because the form field keys that are sent to the server look like this:
topic[name]
topic[linkers_attributes][0][is_active]
topic[linkers_attributes][0][article_attributes][title]
Creating the record is now as simple as:
TopicController < ApplicationController
def create
#topic = Topic.create!(params[:topic])
end
end
A quick GOTCHA for when using has_one in your solution.
I will just copy paste the answer given by user KandadaBoggu in this thread.
The build method signature is different for has_one and has_many associations.
class User < ActiveRecord::Base
has_one :profile
has_many :messages
end
The build syntax for has_many association:
user.messages.build
The build syntax for has_one association:
user.build_profile # this will work
user.profile.build # this will throw error
Read the has_one association documentation for more details.

Resources