Rails 4 API: Creating a nested resource - ruby-on-rails

I have 3 models:
class Repositioning < ActiveRecord::Base
has_one :repo_mood
has_one :mood, through: :repo_mood
end
class Mood < ActiveRecord::Base
has_many :repo_moods
has_many :repositionings, through: :repo_moods
end
class RepoMood < ActiveRecord::Base
belongs_to :repositioning
belongs_to :mood
end
But I only have a Repositionings controller. In my app, the user can add a mood to the repositioning and the data is sent to my API as:
repositioning: { mood: mood_id }
Is there a railsy way of generating the necessary repo_mood entry:
RepoMood.create(repositioning_id: repositioning.id, mood_id: mood_id)
without manually calling it? I'm thinking like nested forms in a Rails view.

You'll want accepts_nested_attributes_for to do this.
It'll allow you to create related models simply by passing the proper attributes to your API endpoint.

Related

Create multiple submodels into the main model's form

Here is the situation :
class Activity < ApplicationRecord
has_many :activity_amenities
end
class Amenity < ApplicationRecord
has_many :activity_amenities
has_many :activities, through: :activity_amenities
end
class ActivityAmenity < ApplicationRecord
belongs_to :activity
belongs_to :amenity
end
So basically i have Activities that have Amenities. The reason for me to use a has_many :through association is because I want to create basic Amenities and each Activity' amenities will have a description proper to the Activity.
So, I want to create new Amenities straight in the creation/edition of an Activity. This image should illustrate it very well :
So when I click on Add amenity button, it should add new select/input group.
Then when I click on create, it should create all the ActivityAmenities and associate them with the Activity model.
Any idea How this should be done (in the Controller and in the View) ? Couldn't find anything really...
ps : note that I'm using simple-form gem for the forms
For all your has_many associations, include the inverse_of option. Then when adding the ActivityAmenities by ID, pass them all in at once as an array. e.g when you make activity_params, pass in activity_amenity_ids[]. Rails will use the automatically generated #activity_amenity_ids= method to handle the creation for you.
Model:
class Activity < ApplicationRecord
has_many :activity_amenities, inverse_of: activity
end
class Amenity < ApplicationRecord
has_many :activity_amenities, inverse_of: amenity
has_many :activities, through: :activity_amenities
end
class ActivityAmenity < ApplicationRecord
belongs_to :activity
belongs_to :amenity
end
Controller:
params.require(:activity).permit(activity_amenity_ids[], :whatever_else)
View: Whatever the name of your input for your form
name="activity[activity_amenity_ids][]"
If you want an explanation of why you you pass in activity_amenity_ids, is basically tl;dr it creates the object if it doesn't exist, and does nothing if it does.
E.g try
Activity.create(name: "Running or whatever", activity_amenity_ids: [1, 2, 3])
And see the result. Notice how a new activity is created along with its associated activity amenities in a single transaction?

Rails, validate has_many through association with attribute value of children

I have three models:
class User < ApplicationRecord
has_many :game_accounts
has_many :favorite_game_accounts, through: :game_account_favorites, source: :game_account
end
class GameAccount < ApplicationRecord
belongs_to :user
has_many :favorite_users, through: :game_account_favorites, source: :user
end
class GameAccountFavorite < ApplicationRecord
belongs_to :user
belongs_to :game_account
validates_presence_of :user, :game_account
validates_uniqueness_of :user, scope: :game_account_id
end
This means that User can have his own GameAccounts and other Users can add them to favorites.
I have added scope in order to prevent one user to have multiple favorites of the same GameAccount. However, there is one problem. User can add to favorite his own GameAccount. How to prevent user adding his own GameAccount to favorites?
I'm not sure that there is any built-in Rails validation for you case, so I'd suggest writing your own custom one.
In your particular case, you can verify on GameAccountFavorite instance, that game_account.user_id isn't equal to user.id.
There's plenty of ways of performing custom validation in Rails

Is: grandparent.parents.children association chaining not correct in Rails 4?

I'm having trouble figuring out the proper way of retrieving all children of multiple parents through association chaining.
To simplify I have three models:
class Customer < ActiveRecord::Base
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
After creating a few objects I tired to use the example from rails guides (association basics: 4.3.3.4 includes):
Customer.first.invoices.line_items
It returns:
undefined method `line_items' for #<Customer::ActiveRecord_Associations_CollectionProxy
Is grandparent.parents.children not usable?
EDIT
I'm not searching for the grandparent.parents.first.children, but all children of all parents in the collection, rails guides state:
If you frequently retrieve line items directly from customers (#customer.orders.line_items),
As a valid operation, I would like to know if that is a mistake.
FINAL As stated in the comments of the selected answer: in ActiveRecord: scopes are chainable but associations are not.
The customer.invoices.line_items cannot work the way you want to, since the has_many always is linked to a single record. but you can achieve what you want (if I understand correctly) using has_many through
as follows:
class Customer < ActiveRecord::Base
has_many :invoices
has_many :line_items, through: :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
and now you can write:
customer.line_items
and it will return all line_items which are connected to a customer's invoices.
Customer.first.invoices.first.line_items
Or if you want all of the data together, you can do something like:
results = Customer.first.invoices.includes(:line_items)
Then you may access data with no DB call, by looping results. For first data ex: results.first.line_items
Hope it helps!
Customer.first.invoices will return an collection (like an array) of invoices. The line_items method isn't defined for a collection, but its defined for an invoice. Try Customer.first.invoices.first.line_items
EDIT - If you always want the orders to include the line items, you can just do:
class Customer < ActiveRecord::Base
has_many :orders, -> { includes :line_items }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end

Rails: accessing associated models with several degrees of separation

I am a beginner in rails, and I have a question about accessing one model from another model that is associated with several degrees of separation.
Let's say I have these models:
Account has_many Spies
Spy has many SpyRelationships and belongs to Account
SpyRelationship belongs to Listing and belongs to Spy
How would I set up the associations so that I could simply pull all the listings associated with a given Account (via its spies and spyrelationships)? What line of code would allow me to do so, after those associations are setup properly?
I'm guessing you want to access a listing through a spy?
class Account < ActiveRecord::Base
has_many :spies
has_many :listings, through: :spies
end
class Spy < ActiveRecord::Base
belongs_to :account
has_many :spy_relationships
has_many :listings, through: :spy_relationships
end
class SpyRelationship < ActiveRecord::Base
belongs_to :listing
belongs_to :spy
end
class Listing < ActiveRecord::Base
has_many :spy_relationships
end

Association dilemma

What is the best way to implement something like a board in pinterest (a collection of objects) in rails. I am trying to come with it, it seems more like an array implementation.
Here's my logic for the associations : User have many collections, user have many pins , collections belongs to user.
User class
class User < ActiveRecord::Base
has_many :pins, through: :collections
has_many :collections
end
Pins class
class Pin < ActiveRecord::Base
belongs_to :user
has_many :collections
end
Collections class
class Collection < ActiveRecord::base
belongs_to :user
end
So now here's my confusion, how to implement a controller that will allow me to create a collection and inside this collection object, create or push pins and save them as another object for the current_user. Hope I'm making sense
Here's the controller
class CollectionsController < ApplicationController
def create
#collection = current_user.collections.new(params[:collection])
#this where i'm confused , if it an array , how to implement it , to push or create a pin object inside ?
end
end
You have to use nested attributes for this.
Check this http://currentricity.wordpress.com/2011/09/04/the-definitive-guide-to-accepts_nested_attributes_for-a-model-in-rails-3/.
Basically what you need is:
# collection model
accepts_nested_attributes_for :pins
# view, see also nested_form in github
f.fields_for :pins
You're looking for the has_many_through association. See section 2.4 on the Rails guide: http://guides.rubyonrails.org/association_basics.html
class User < ActiveRecord::Base
has_many :collections
end
class Pin < ActiveRecord::Base
has_many :collections, through: :pinnings
end
class Pinning < ActiveRecord::Base
belongs_to :pin
belongs_to :collection
end
class Collection < ActiveRecord::base
belongs_to :user
has_many :pins, through: :pinnings
end

Resources