Selecting 'has_many through' associations in Active Admin - ruby-on-rails

I've seen several questions asked about this along the same vein, e.g.
Using HABTM or Has_many through with Active Admin
but I'm still struggling to get things to work (I've tried multiple ways at this point).
My models (slightly complicated by the 'technician' alias for the user model):
class AnalysisType < ActiveRecord::Base
has_many :analysis_type_technicians, :dependent => :destroy
has_many :technicians, :class_name => 'User', :through => :analysis_type_technicians
accepts_nested_attributes_for :analysis_type_technicians, allow_destroy: true
end
class User < ActiveRecord::Base
has_many :analysis_type_technicians, :foreign_key => 'technician_id', :dependent => :destroy
has_many :analysis_types, :through => :analysis_type_technicians
end
class AnalysisTypeTechnician < ActiveRecord::Base
belongs_to :analysis_type, :class_name => 'AnalysisType', :foreign_key => 'analysis_type_id'
belongs_to :technician, :class_name => 'User', :foreign_key => 'technician_id'
end
I have registered an ActiveAdmin model for the AnalysisType model and want to be able to select (already created) Technicians to associate with that AnalysisType in a dropdown/checkbox. My ActiveAdmin setup currently looks like:
ActiveAdmin.register AnalysisType do
form do |f|
f.input :analysis_type_technicians, as: :check_boxes, :collection => User.all.map{ |tech| [tech.surname, tech.id] }
f.actions
end
permit_params do
permitted = [:name, :description, :instrumentID, analysis_type_technicians_attributes: [:technician_id] ]
permitted
end
end
Whilst the form seems to display okay, the selected technician does not get attached upon submitting. In the logs I'm getting an error 'Unpermitted parameters: analysis_type_technician_ids'.
I've tried multiple ways of doing this following advice in other related SO pages but am always coming up against the same issue, i.e. unpermitted parameterd of some nature. Can anyone point out what I am doing wrong? (I'm using Rails 4 by the way)

Managing the association via has_and_belongs_to_many or has_many relations
does not require the use of accepts_nested_attributes_for. This type of form
input is managing the Technician IDs associated with the AnalysisType record.
Defining your permitted parameters and form like the following should allow
those associations to be created.
ActiveAdmin.register AnalysisType do
form do |f|
f.input :technicians, as: :check_boxes, collection: User.all.map { |tech| [tech.surname, tech.id] }
f.actions
end
permit_params :name, :description, :instrumentID, technician_ids: []
end
In the case where the creation of new Technician records is required that is
when the accepts_nested_attributes_for would be used.
Note: Updated answer to match comments.

Related

Nested whitelisted attributes still unpermitted

I have three models: event, event_user, event_users_day.
event accepts nested attributes event_user which accepts event_users_day as a nested attributes as well.
class Event < ActiveRecord::Base
has_many :event_users, :dependent => :destroy, :inverse_of => :event
accepts_nested_attributes_for :event_users, :allow_destroy => true
end
class EventUser < ActiveRecord::Base
belongs_to :event, :inverse_of => :event_users
has_many :event_users_days, :dependent => :delete_all
accepts_nested_attributes_for :event_users_days, :allow_destroy => true
end
class EventUsersDay < ActiveRecord::Base
belongs_to :event_users, inverse_of: :event_users_days
validates :event_users, :presence => true
end
The simple nested form is pretty straight forward:
= simple_nested_form_for :event_users do |f|
= f.fields_for :event_users_days do |day|
= day.input :event_day_id, as: :check_boxes, collection: #daygroups
= f.submit :class => "btn btn-success"
In my controller event_user and the attributes for event_users_days are whitelisted:
#event_user = EventUser.new(params.permit(:event_id), params[:event_users].permit(:id, event_users_days_attributes: [:id, :event_day_id]))
But when I save it only the EventUser is saved as the server tells me that event_users_days is not permitted:
Unpermitted parameter: event_users_days
Any ideas of what am I doing wrong?
The Unpermitted parameter error is being literal, so your form is generating a event_users_days parameter instead of the expected event_users_days_attributes parameter, which is being rightly rejected by Rails.
I haven't used nested_form in a long time, and if you're using Rails 4 then I'm not sure that's going to be the best pick (and it's not necessary), but even so I think the problem is that you're using :event_users instead of #event_users - but generally I'd recommend switching to simple_form unless you're in an old Rails (and if you are then you should specify that when asking questions on SO).

Rails 4 collection_select from another model

My model has the following (relevant) declartions:
class Campaign < ActiveRecord::Base
has_many :placements, dependent: :destroy
has_many :clients, dependent: :destroy
accepts_nested_attributes_for :clients, allow_destroy: true
accepts_nested_attributes_for :placements, allow_destroy: true
end
class Placement < ActiveRecord::Base
has_one :client, through: :campaign
belongs_to :campaign
validates_presence_of :hashtags_instagram, :hashtags_twitter, :sms, :url, :channel
end
class Client < ActiveRecord::Base
belongs_to :campaign
belongs_to :placement
validates_presence_of :client_name
end
So effectively, campaigns have many placements and clients. Placements have exactly one client, and are associated with exactly one campaign (or at least should be, I'm still not sure I did that correctly).
So in my placement form, I want it to display a dropdown menu of all of the clients belonging to the placement's campaign (not all campaigns). If it is a new placement, it should display "Please Select or similar", otherwise, it should display the client belonging to that placement.
I can't seem to get the :prompt option to work though. It never actually displays a Please Select, just the first client in the list. I was attempting to use this to get it to work:
Oddly, if I change prompt to include_blank, it does actually show the blank item (although it still selects the first client in the list, not the blank item).
When I tried some things, I tried the code:
= f.collection_select(:placement_id, #placement.client.all.to_a, :id, :client_name, {:prompt => true}, class: "newdropdown-menu", id: "newdropdown")
And got the error message:
Could not find the source association(s) :client or :client in model Campaign. Try 'has_many :client, :through => :campaign, :source => <name>'. Is it one of :user, :placements, :clients, or :photo?
If I change the collection select to read:
= f.collection_select(:campaign_id, #placement.campaign.clients.all.to_a, :id, :client_name, {:prompt => true}, class: "newdropdown-menu", id: "newdropdown")
Then it runs, but does not actually display the prompt, just the list.
Changing #placement to placement does not seem to have any effect (which seems odd).
I don't want to change it to has_many, since placements should have exactly one client. How should I proceed? I tried messing with :source, but it doesn't seem to relate to my problem.
You need to place you html options in a hash:
f.collection_select(:campaign_id, #placement.campaign.clients.all, :id, :client_name, {:prompt => true}, {class: "newdropdown-menu", id: "newdropdown"})
Documentation available here:
http://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/collection_select

Product orders between 2 users

I have three models: User, Product, Offer and a problem with the relationship between these models.
Scenario:
User 1 posts a product
User 2 can send User 1 an offer with an price e.g $ 10
User 1 can accept or reject the offer
My questions are now:
What is the right relationship between User, Product and Offer?
How can I handle those "accept or reject" actions?
Is there maybe a better solution?
User model:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :avatar, :screen_name
has_many :products
has_many :offers,:through => :products
end
Product model:
class Product < ActiveRecord::Base
attr_accessible :content, :price, :title, :tag_list, :productimage, :user_id
belongs_to :user
has_many :offers, :through => :users
end
Offer model:
class Offer < ActiveRecord::Base
attr_accessible :offer_price, :status, :user_id, :product_id
has_many :products
has_many :users, through: :products
end
Thanks in advance :)
EDIT:
I am using Rails 3.2.8
Warning: here comes a small novel.
Part 1: setting up the associations
I'd recommend reading the Rails guide on associations thoroughly, bookmark it, and read it again, because this is a key thing to understand properly, and can be a bit tricky - there are lots of options once you go beyond basic associations.
One thing to notice about your app is that your users have two roles, buyers and sellers. You're going to need to be careful with the names of your associations - Does #user.offers return the offers the user has made, or the offers the user has received? You might want to be able to put lists of both these things in the user's profile.
The basic relationships you're describing are fairly simple:
A user can sell many products, so User has_many :products and Product belongs_to :user
A user can make many offers, so User has_many :offers and Offer belongs_to :user
A product may receive many offers so Product has_many :offers and Offer belongs_to :product
That's all well and good, and you could certainly get by just doing this - in which case you can skip down to Part 2 :)
However, as soon as you start trying to add the through relationships the waters are going to get muddy. After all,
Offer belongs_to :user (the buyer), but it also has a user through product (the seller)
User has_many :products (that they are selling), but they also have many products through offers (that they are buying - well, trying to buy).
Aargh, confusing!
This is the point when you need the :class_name option, which lets you name an association differently to the class it refers to, and the :source option, which lets you name associations on the 'from' model differently to the 'through' model.
So you might then form your associations like this:
# User
has_many :products_selling, class_name: 'Product'
has_many :offers_received, class_name: 'Offer',
through: :products_selling, source: :offers
has_many :offers_made, class_name: 'Offer'
has_many :products_buying, class_name: 'Product',
through: :offers_made, source: :product
# Product
belongs_to :seller, class_name: 'User', foreign_key: :user_id
has_many :offers
has_many :buyers, class_name: 'User', through: :offers
# Offer
belongs_to :product
belongs_to :buyer, class_name: 'User', foreign_key: :user_id
has_one :seller, class_name: 'User', through: :product
Although if you renamed your user_id columns to seller_id in the products table, and buyer_id in the offers table, you wouldn't need those :foreign_key options.
Part 2: accepting/rejecting offers
There's a number of ways to tackle this. I would put a boolean field accepted on Offer and then you could have something like
# Offer
def accept
self.accepted = true
save
end
def reject
self.accepted = false
save
end
and you could find the outstanding offers (where accepted is null)
scope :outstanding, where(accepted: nil)
To get the accept/reject logic happening in the controller, you might consider adding new RESTful actions (the linked guide is another one worth reading thoroughly!). You should find a line like
resources :offers
in config/routes.rb, which provides the standard actions index, show, edit, etc. You can change it to
resources :offers do
member do
post :accept
post :reject
end
end
and put something like this in your OffersController
def accept
offer = current_user.offers_received.find(params[:id])
offer.accept
end
# similarly for reject
Then you can issue a POST request to offers/3/accept and it will cause the offer with id 3 to be accepted. Something like this in a view should do it:
link_to "Accept this offer", accept_offer_path(#offer), method: :post
Note that I didn't just write Offer.find(params[:id]) because then a crafty user could accept offers on the behalf of the seller. See Rails Best Practices.
Your models are good enough, except for the relations. The confusion starts when you are trying to differentiate the owned products vs interested products(offered) and product owner vs interested users(users who placed the offer). If you can come up with a better naming convention, you can easily fix it.
1. Better relations
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :avatar, :screen_name
has_many :owned_products, :class_name => "Product"
has_many :offers
has_many :interested_products, :through => :offers
end
class Offer < ActiveRecord::Base
attr_accessible :offer_price, :status, :user_id, :product_id
belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
end
class Product < ActiveRecord::Base
attr_accessible :content, :price, :title, :tag_list, :productimage, :user_id
belongs_to :owner, :foreign_key => :user_id, :class_name => "User"
has_many :offers
has_many :interested_users, :through => :offers
end
With these relations I think you can get all the basic information you would be interested.
For Example,
#product = Product.find(1)
#product.owner # would give you the user who created the product
#product.interested_users # would give you users who placed an offer for this product
#user = User.find(1)
#user.owned_products # would give you the products created by this user
#user.interested_products # would give you the products where the user placed an offer
2. Handling accept and reject actions.
From your description, I see there can be 2 possible state changes to an offer, "created" -> "accept" or "created" -> "reject". I suggest you to look at state_machine. State machine will add nice flavor to your model with its helper methods, which I think will be very useful in your case. So your Offer model will look something like this,
class Offer < ActiveRecord::Base
# attr_accessible :title, :body
attr_accessible :offer_price, :status, :user_id, :product_id
belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
state_machine :status, :initial => :created do
event :accept do
transition :created => :accepted
end
event :reject do
transition :created => :reject
end
end
end
#cool helper methods
#offer = Offer.new
#offer.accepted? #returns false
#offer.reject #rejects the offer
#offer.rejected? #returns true
I hope this gives you a better picture.
How about
class User < ActiveRecord::Base
has_many :products # All products posted by this user
has_many :offers # All offers created by this user
end
class Product < ActiveRecord::Base
belongs_to :user # This is the user who posts the product (User 1)
has_many :offers
end
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :user # This is the user who creates the offer (User 2)
# Use a 'state' field with values 'nil', 'accepted', 'rejected'
end
For your scenario:
# User 1 posts a product
product = user1.products.create
# User 2 can send User 1 an offer with an price e.g $ 10
offer = user2.offers.create(:product => product)
# User 1 can accept or reject the offer
offer.state = 'rejected'
You could refine this depending on your needs - e.g. if the same product could be posted by different users.

uninitialized constant [Incident::Involf]

I have a rails app that is using Active Admin. Basic flow is that I have an Incident model has associations with other models. Connections other models seem fine but for one model (Involves) in particular I get the error uninitialized constant Incident::Involf when creating a new Incident.
Incident model looks like this
class Incident < ActiveRecord::Base
# Relationships
belongs_to :admin_user
has_many :images
has_and_belongs_to_many :incident_types
has_and_belongs_to_many :involves
has_and_belongs_to_many :special_considerations
belongs_to :county
belongs_to :location
# Nested Attributes
accepts_nested_attributes_for :images
accepts_nested_attributes_for :county
accepts_nested_attributes_for :location
accepts_nested_attributes_for :incident_types
accepts_nested_attributes_for :involves
accepts_nested_attributes_for :special_considerations
end
The Involve model looks like this
class Involve < ActiveRecord::Base
# Relationships
has_and_belongs_to_many :incidents
# Aliases
alias_attribute :name, :involved
end
The active admin model for nested Incidents look like this
f.inputs "Vehicles Involved" do
f.input :involves, :as => :check_boxes
end
f.inputs "Special Considerations" do
f.input :special_considerations, :as => :check_boxes
end
Where Special Considerations work if I have f.input :involves, :as => :check_boxes commented out, but I get this error if I don't have it commented out.
Looked at the database and the associations and the code is very similar to others that I am not sure what the problem is.
This issue is resolved.
It looked liked to be an issue with either the pluralization of Involve or involve is a keyword.
I changed the model name to Incident_Involvement and that resolved the issue.

Accessing two sides of a user-user relationship in rails

Basically, I have a users model in my rails app, and a fanship model, to facilitate the ability for users to become 'fans' of each other.
In my user model, I have:
has_many :fanships
has_many :fanofs, :through => :fanships
In my fanship model, I have:
belongs_to :user
belongs_to :fanof, :class_name => "User", :foreign_key => "fanof_id"
My fanship table basically consists of :id, :user_id and :fanof_id. This all works fine, and I can see what users a specific user is a fan of like:
<% #user.fanofs.each do |fan| %>
#things
<% end %>
My question is, how can I get a list of the users that are a fan of this specific user?
I'd like it if I could just have something like #user.fans, but if that isn't possible what is the most efficient way of going about this?
Thanks!
Add in User model:
has_many :my_fanclubs, :class_name => 'Fanship', :foreign_key => 'fanof_id'
has_many :fans, :through => :my_fanclubs, :source => :user, :class_name => 'User'
(not tested)

Resources