Polymorphic association with condition - ruby-on-rails-4

We have models as below...
class Post
has_many: :comments, as: :commentable
end
class Media
has_many: :comments, as: :commentable
end
class Comment
belongs_to: :commentable, polymorphic: true
belongs_to: :post, foreign_key::post_id, foreign_type: 'Post'
end
Exampale
Post1 has Comment3 => {id: 3, text: "Some comment 3", commentable_id: 1, commentable_type: 'Post'}
Media2 has Comment4 => {id: 4, text: "Some comment 4", commentable_id: 2, commentable_type: 'Media'}
When I want to access Comment3.post it gives result as expected.
But When some how want access Comment4.post it brings an object from post table which has id = 2, but expected nil, coz Comment4 does not belongs to any post.
We can get from below method inside Comment model, but want as an association.
def post
self.commentable if self.commentable_type == 'Post'
end
Could not get what I expected.., Please help here...

I like your approach
def post
self.commentable if self.commentable_type == 'Post'
end
But if you really need it in the association way, then you can try with
belongs_to :post, foreign_key: :commentable_id, foreign_type: :commentable_type, polymorphic: true
OR
belongs_to :post, -> (record){ record.commentable_type == 'Post' ? joins(:comments).where(comments: {commentable_type: 'Post'}) : where("false") }, foreign_key: :commentable_id
Hope, it helps.

Related

Rails & nested comments through partials, strange recursion

I've got a pretty simple setting for (polymorphic) comments model which also have a has_many relationship wih itself (called :replies):
# comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
default_scope -> { order(created_at: :asc) }
belongs_to :user
belongs_to :comment_parent, class_name: "Comment", foreign_key: "comment_id"
has_many :replies, class_name: "Comment", foreign_key: "comment_id"
validates :content, presence: true
validates :commentable, presence: true
end
In the post controller I fetch the comments:
#comments = #commentable.comments.all()
I'm initiating the partial on my blog view page like so:
= render #comments
And finally the comment/_comment partial containing:
(simpiflied code, this only shows gravatar & username)
# comment/_comment.html.haml
%ol.media-list
%li.media{:class => ("media-replied" if comment.comment_id)}
= link_to gravatar_for(comment.user, size: 80, class_name: 'media-object img-circle'), comment.user, class: 'pull-left'
= render partial: 'comments/comment', collection: comment.replies
Somehow this creates some strange recurrence, in which replies get shown on their correct location, but also on an incorrect location. Problem is that there doesn't seem to be any logic in this. (at least for me it doesn't)
I've checked console to verify that the database contains the correct relations between the records (no faulty records), so it must be in the presentation / partial call.
Any ideas?
Ok, that was my own stupid mistake of course... the call #commentable.comments.all() result is all the comments, so even the nested comments are shown as if they are top level.
Short term fixed it by adding where(comment_id: nil)

rails 4 nested attributes won't create has_many model

I'm new to rails and have spent way too many hours on this. Thanks a lot, in advance, for any help!
I can't seem to get fields_for and/or accepts_nested_attributes_for to work for my nested attributes.
I have a smash_client that has_many contracts and a form that tries to create a smash_client with a parameter and at the same time it tries to also set a parameter on the contract object. The contract belongs_to the smash_client.
I've tried a lot of different solutions and have read the docs but I'm still missing something. I get this in my params hash, in the smash_clients_controller.rb
..., "smash_client"=>{"name"=>"fasdf", "user"=>"adam"}, "smash_client_id"=>{"instance_type"=>"spot"},...
from
= form_for #smash_client do |f|
.field
= f.label :name
= f.text_field :name
.field
= fields_for :smash_client_id do |c|
%p
= c.radio_button :instance_type, 'spot'
= c.label :instance_type, 'spot'
= c.radio_button :instance_type, 'on_demand'
= c.label :instance_type, 'on demand'
.actions
= f.submit 'Save'
and
class SmashClient < ActiveRecord::Base
has_many :contracts, dependent: :destroy
accepts_nested_attributes_for :contracts, allow_destroy: true,
reject_if: proc { |attributes| attributes[:instance_type].blank? }
...
def new
#smash_client = SmashClient.new
3.times { #smash_client.contracts.build }
end
...
def smash_client_params
#smash_client_params = params.require(:smash_client).
permit( :user, :name, contracts_attributes: [:instance_type] )
end
end
and
class Contract < ActiveRecord::Base
belongs_to :smash_client
after_create :determine_instance_type_and_start
before_destroy :stop_instances
...
end
I think the nested params would work if I hard coded it because if I try something like this, in the console, I don't get errors and I get a new SmashClient and Contract.
smash_client_params = {name: 'something', user: 'blah', contracts_attributes: [{instance_type: 'spot'}]}
SmashClient.create( smash_client_params )
I tried using :contracts, #smash_client.contracts and a few other things in the fields_for section. Also tried using select and collection_select but I can't seem to nail down the form.
sorry for the long post. Hopefully I got all the useful info with nothing extra in the question.
I'd really appreciate some direction or answers.
Thanks in advance.
I finally found it. The :instance_type had to be whitelisted in the Contract model. Thanks again, kalyani. I appreciate the help. Here's the changes to the code above:
.field
= fields_for :contracts do |c|
= c.label :instance_type, 'spot instance'
= c.radio_button :instance_type, 'spot', checked: true
= c.label :instance_type, 'on demand instance'
= c.radio_button :instance_type, 'on_demand'
and
def contract_params
params.require(:contract).
permit(:id, :name, :instance_id, :smash_client_id, :instance_type)
end
Instead of : fields_for :smash_client_id do |c|
write it as: fields_for :contracts do |c|
Refer:
1. http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://railscasts.com/episodes/196-nested-model-form-part-1
Rails 4 Nested Attributes Unpermitted Parameters ---- refer this for writing the code in controller and view the correct way

Rails - Multiple Nested Attributes

I am trying to use a select tag to save multiple nested children in a parent.
This is the error I'm looking at
Couldn't find all UserLocations with IDs (1, 2) (found 0 results, but was looking for 2)
I have the following setup under Rails 4 and Devise:
User
class User < ActiveRecord::Base
has_many :user_locations
accepts_nested_attributes_for :user_locations, :allow_destroy => true
end
UserLocation (locations that the user has)
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
Location (predefined list of locations the user can choose upon)
class Location < ActiveRecord::Base
has_many :user_locations
has_many :users, through: :user_locations
end
However, when trying to save the selected UserLocations, they won't be saved.
Rails Select Tag (You can choose multiple items)
<%= f.select :user_location_ids, options_for_select(Location.all.collect { |l| [ l.name, l.id ] }, #user.user_locations.collect{ |l| l.id }), {}, { multiple: true } %>
I have put the user_location_ids in my application_controller as user_location_ids: []
Cheers
Solved
The solution is to overwrite the default setter method for multiple nested models model_ids=(value). Do not use the plural of the model, e.g. models_ids=(value), because that is wrong!
def user_location_ids=(value)
for slot in value do
unless slot == ""
location = Location.find_by(id: slot.to_i)
unless location.nil?
self.user_locations << UserLocation.create(user_id: self.id, location_id: location.id)
end
end
end
end

How to whitelist dynamic hstore keys in a nested model

I have these relationships:
class Applicant < ActiveRecord::Base
has_many :answers
accepts_nested_attributes_for :answers
end
class Answer < ActiveRecord::Base
belongs_to :applicant
end
The answer model has an hstore attribute called properties. The properties hash will have dynamic keys as they are created in the app by the user.
I can't seem to successfully whitelist these dynamic keys within the applicants controller.
This is my current (unsuccessful) attempt.
def applicant_params
params.require(:applicant).permit(:answers_attributes: [:question_id, :id]).tap do |whitelisted|
whitelisted[:answers_attributes][:properties] = params[:applicant][:answers_attributes][:properties]
end
end
Thanks for any help.
UPD. Try to use following approach (tested in separate file):
#params = ActionController::Parameters.new(
applicant: {answers_attributes: {
"0" => {question_id: 10, id: 110, properties: {a: "b", c: "d"}},
"1" => {question_id: 20, id: 120, properties: {m: "n", o: "p"}}
}})
def applicant_params
#properties should be [:a, :c, :m, :o]
properties = []
#params[:applicant][:answers_attributes].values.each do |answer|
properties |= answer[:properties].keys
end
#params.require(:applicant).permit(answers_attributes:
[:question_id, :id, properties: properties])
end
BTL. There is pretty good article on working with hstores. And some general things on using hstore in Rails 4.

Joining two Models in Rails 4

I have a Location model and an Answer model and I'm trying to join the two, so that when an Answer is created, it is associated with the Location.
I have added in the appropriate has_one and belongs_to associations, added a migration to add Locations id to the Answer table.
Trouble is when an Answer gets created, the 'location_id' is nil.
Any help would be greatly appreciated. Here's my files:
Location Model
class Location < ActiveRecord::Base
require 'Geoip-rails'
has_one :answer
def self.get_location
geoip = GeoIP.new("lib/GeoLiteCity.dat")
l = Location.new
l.city = geoip.country('78.151.144.93').city_name
l.save
end
end
Answer Model
class Answer < ActiveRecord::Base
belongs_to :location
belongs_to :question
belongs_to :choice
has_one :question, :through => :choice
end
Add location to Answers migration
class AddLocationToAnswers < ActiveRecord::Migration
def change
add_column :answers, :location_id, :integer
add_index :answers, :location_id
end
end
Questions controller - Answer action
def answer
#choice = Choice.find(params[:id])
#location = Location.get_location
#answer = Answer.create(:question_id => #choice.question_id, :choice_id => #choice.id)
redirect_to questions_url
end
Update:
I have now changed around the association from Mischa's comments.
Now in the console I can do this and join the Models:
1.9.3-p392 :013 > a = Answer.last
Answer Load (0.2ms) SELECT "answers".* FROM "answers" ORDER BY "answers"."id" DESC LIMIT 1
=> #<Answer id: 13, question_id: 1, choice_id: 2, created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28", location_id: nil>
1.9.3-p392 :014 > a.location
=> nil
1.9.3-p392 :015 > a.location = Location.last
Location Load (0.2ms) SELECT "locations".* FROM "locations" ORDER BY "locations"."id" DESC LIMIT 1
=> #<Location id: 25, lat: nil, lon: nil, city: "London", created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28">
1.9.3-p392 :016 > a
=> #<Answer id: 13, question_id: 1, choice_id: 2, created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28", location_id: 25>
1.9.3-p392 :017 >
I've tried achieving this in 'answer' action in the controller as follows:
#answer = Answer.create(:question_id => #choice.question_id, :choice_id => #choice.id, :location_id => #location.id)
But :location_id => #location.id give me an error:
undefined method `id' for true:TrueClass
I don't know how to combine the two models in the controller
Solved
For anyone interested. My get_location method wasn't doing what it should. I had to call the object l after it had been saved. 5 hours of head banging and all that was needed was 'l'.
def self.get_location
geoip = GeoIP.new("lib/GeoLiteCity.dat")
l = Location.new
l.city = geoip.country('78.151.144.93').city_name
l.save!
l
end