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
Related
New to Rails so go easy on me :-)
I have 2 models: User and Role:
class User < ActiveRecord::Base
has_many :roles
accepts_nested_attributes_for :roles
validates_presence_of :role_id
end
class Role < ActiveRecord::Base
belongs_to :user
end
User has a role_id for the foreign key.
All I'm trying to do is be able to select a role for the user in the users/new form. I know it's easy, but I cannot seem to figure it out...I've literally read for hours today trying to figure it out. The drop down select list appears in the view, but it always fails validation (like it shows up, but never actually associates what the user selects with the User.role_id)
Here is what I have in my form partial to show the drop down:
<%= f.collection_select :role_id, Role.all, :id, :name %>
Can anyone point me in the right direction? Maybe I have to use some sort of nested forms, but nothing I have tried seems to work and this is what I currently have. Do I have to do something in my controller?
If User has many roles, your User model must not have a field: user_id, I think, and I hope, that Users Have and Belongs to many Roles. Then you need a third model:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, through: :user_roles
end
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
validates_presence_of :role_id, :user_id
end
In your User form you can use this to update relations (look: :role_ids in plural)
<%= f.collection_select :role_ids, Role.all, :id, :name, {}, {multiple: true} %>
And the validation is now in UserRole model.
Edit: If you are using Rails 4.x you need to permit params for a collection of role_ids.
params.require(:user).permit(:user_field1, :user_field2, ... , role_ids: [])
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.
I have 3 models:
class Brand
attr_accessible :obs, :site, :title
has_many :equipments
end
class Equipment
attr_accessible :brand_id, :category_id, :lending_id
belongs_to :brand
has_many :lendings
end
class Lending
attr_accessible :equipment_id
belongs_to :equipment
end
I'm trying to show the brand of an associated equipament:
Brand: <%= #lending.equipment.brand %>
that command show this: Brand:0xab7f2c8
As you can see, there's no association between brand and lending models and for me its strange if i do that. I want to use the equipment/brand association to retrieve the :title information and show it on my lending view.
Can anyone help me?
You can either use a delegate in Lending:
delegate :brand, :to => :equipment, allow_nil: true
Or you can setup a has-one-through association in Lending:
has_one :branch, :through => :equipment
Either way, you can now call branch directly from a Lending instance, and work on it (almost) as if it were a regular association.
Use delegate
class Lending
attr_accessible :equipment_id
belongs_to :equipment
delegate :brand, :to => :equipment, :allow_nil => true
end
now you can use
<%= #lending.brand.title%>
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.
I have three tables via many-to-many-association: Supermarket, Product and Supply.
Each Supermarket can hold many products and each product can be sold in many supermarkets. The association is build via the Supply-model.
Supermarket:
class Supermarket < ActiveRecord::Base
attr_accessible :name, :address, :products_attributes
has_many :supplies
has_many :products, :through => :supplies
accepts_nested_attributes_for :products
end
Product:
class Product < ActiveRecord::Base
attr_accessible :name, :supermarkets_attributes
has_many :supplies
has_many :supermarkets, :through => :supplies
accepts_nested_attributes_for :supermarkets
end
Association via Supply:
class Supply < ActiveRecord::Base
attr_accessible :supermarket_id, :product_id
belongs_to :supermarket
belongs_to :product
end
I have created the scaffolds and populated the Supermarket-table.
In my Product form, i want to use one (or more) drop-down-menu(s) to select the correspondent Supermarket-name(s). Goal is to create a new product while also creating the association via the Supply-table.
What should the code look like in form and/or controller for the products if I want to select the corresponding supermarkets from there?
In you products form you need to add this line...
<%= collection_select(:product, :supermarket_ids, SuperMarket.all, :id, :name, {}, { :multiple => true } )%>
You also shouldn't need to use an accepts_nested_attributes for this, the many to many association you already have set up should take care of the rest.
I think in views
<%= f.collection_select "super_market_ids[]",#super_markets,:id,:name,{},{:multiple=>"multiple'} %>
I am not sure about super_market_ids or super_market_ids[] and syntax just verified once.
In select tag if you want checkbox type multi select there is an chosen library that will help you to build nicer UI,