I've a simple question about active admin interface.
In my application, I've a resource added to the active_admin. When I access the resource from active_admin, I get all records for that resource. When I select/access (as a show action) one record it shows details of that instance and all belongs_to associations but I don't know how to get the has_many or has_one association details in the show view?
Any ideas? I appreciate any feedback.
Thanks,
Atarang.
You need to customize your show screen in app/admin/yourresource.rb. You shouldn't need to do anything special otherwise, other than making sure the has_many and belongs_to associations are correct. For example, if you have a category with many items, do this in category.rb:
show :category do
panel "Category Info" do
attributes_table_for category, :name, :created_at
end
panel "Items in This Category" do
table_for(category.items) do
column("Name", :sortable => :name) {|item| item.name }
column("Created At") {|item| item.created_at }
end
end
end
There are more good examples here and elsewhere in the source for the demo project, which for some reason is hard to find from the main site.
Related
I have models in my Rails 5 app for User, Proposal and Potential.
Users create Proposals which they themselves and others can then create comments on.
The associations between models are:
User
has_many :proposals, dependent: :destroy
has_many :potentials
Proposal
belongs_to :user
has_many :potentials, inverse_of: :proposal
accepts_nested_attributes_for :potentials, reject_if: :all_blank, allow_destroy: true
Potential
belongs_to :proposal, inverse_of: :potentials
belongs_to :user
In my routes file, I have two resources for potentials. I'm not sure if I've gone off piste with this bit- I cant find an example of how to do this otherwise. I have both:
resources :potentials
as well as:
resources :proposals do
resources :potentials
The reason I have done this is so that when the user is creating a proposal, they can use a nested fields form to add potential attributes. When another user sees that proposal, they get a new form to add a :potential set of attributes (they don't do it via the proposal form).
In my potentials view folder, I have views for new which renders form for as well as potential_fields_for which is incorporated in my proposals form (only the proposal creator can use the nested fields).
The new/render form has:
<%= simple_form_for [ #proposal, #potential ] do |f| %>
f
The proposal form has:
<%= f.simple_fields_for :potentials do |f| %>
<%= f.error_notification %>
<%= render 'potentials/potential_fields', f: f %>
<% end %>
<%= link_to_add_association 'Add another novel aspect', f, :potentials, partial: 'potentials/potential_fields' %>
</div>
In my proposals controller, I'm trying to find a way to exclude 3rd party created :potentials from the fields displayed in the proposal form.
def edit
#proposal.potentials_build unless #proposal.potentials || #proposal.potential.user_id != current_user.id
I don't want the proposal creator to be able to edit those fields from the proposal form but even if I don't touch them, the user id on the 3rd party potential gets updated to the proposal creator id when I update the proposal form (without updating those specific 3rd party potential attributes).
I tried to change the edit action in the proposals controller be excluding potentials created by a user that is not the current user id. Only the proposal creator can edit the proposal, so I expect that this will exclude instances of proposal.potential that have a user id other than the proposal.user_id.
That doesnt work.
Is there a way that I can limit the proposal#edit action to only those potential instances that are not created by the proposal creator?
TARYN'S SUGGESTION
I tried to adopt Taryn's thoughts about this.
In my proposal.rb, I made 2 scopes:
scope :owner_potentials, ->{ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->{ where(user_id: != potential.user_id) }
I am not confident that this is the correct way to write a scope. Although I cant find a reference to how to write them, on previous occasions when I have tried to learn how to compose them, I have received advice to set them out in this format:
scope :owner_potentials, ->(user){ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->(user){ where(user_id: != potential.user_id) }
I tried this way as well, but I get the same error as I do if I don't include "(user)". I don't understand what that inclusion does or should do.
In my proposal controller, edit action, I have then tried each of the following:
# #proposal.potentials_build unless #proposal.potentials || #proposal.potential.user_id != current_user.id
# #proposal.owner_potentials.build unless #proposal.owner_potentials
##potentials = #proposal.owner_potentials || #proposal.owner_potentials.build
The first one was my original attempt. It didnt work and prompted me to write this post.
The second one is the way I think it should be written.
The third is just incorporating Taryn's idea directly to see if that's how it should be written (although I think that was more of general way of describing what to do).
None of these work. In the case of the 2nd version of this attempt, I get an error that says:
NoMethodError at /proposals/17/edit
undefined method `owner_potentials' for #<Proposal:0x007f84f6ca1700>
I think the reason why this isnt working is that the scope is run on the class as a table, not the specific instance to be edited. I think this post explains that.
Proposals have many potentials, so the idea is to check all of the potentials belonging to the specific proposal to see whether any of those potentials have the user id that is the same as the user id on the proposal instance. How can I do that?
I cant write:
Proposal.owner_potentials.build unless Proposal.owner_potentials
in the proposal controller edit action because the set_proposal method is picking out the correct proposal for edit to apply on.
Can I use a scope in a controller edit action where there is a has_many relationship that is being tested by the scope?
NEXT ATTEMPT
My next thought is to try to define the scopes in the Potential model so that the scope can run on the class.
I tried:
scope :owner_potentials, ->{ where('user_id == potential.proposal.user_id' ) }
scope :third_party_potentials, ->{ where('user_id != potential.proposal.user_id') }
I spent hours on codementor trying to learn scopes and my takeaway from that session is the syntax i used above is incorrect - but the way I was shown to write them (which is at the top of the post, gives an error with the undefined variable). I don't know how to figure out how to learn to write a scope.
Anyway- next I tried changing the edit action in my Proposal controller to:
#proposal.potentials.owner_potentials.build unless #proposal.potentials.owner_potentials
Now, I get no errors when I save this and try it, but when I try to edit the proposal, I can still edit potentials that have been created by third parties. I don't understand whether Im not writing the scope effectively, or if this solution isnt going to work for another reason.
I would consider adding a custom scoped association eg owners_potentials/third_party_potentials that limits potentials to those created by the owner/ by somebody other than the owner. You can then use these scopes whenever you need them.
eg
has_many :potentials, inverse_of: :proposal
has_many :owner_potentials, ->{ where("potentials.user_id = proposals.creator_id") }
has_many :third_party_potentials, ->{ where("potentials.user_id != proposals.creator_id") }
Then you could do something like:
def edit
#potentials = #proposal.owner_potentials || #proposal.owner_potentials.build
Then in the form, be specific about using the one you've specified:
<%= f.simple_fields_for #potentials do |f| %>
Note: code has not been tested or sanity-checked, it's here to give an idea of the kind of thing you can do - getting it to actually work is left as an exercise for the reader ;)
Is there a quick and easy method/library to replace an ActiveAdmin resource's belongs_to association (Formtastic) input that meets these requirements?
It does not load every record from the table onto the page. That's the problem with the default select dropdown. If my Users table has 100k records, it has to render every record's :name and :id into the dropdown.
It autocompletes or provides some AJAX-like feedback that I'm selecting an associated record that actually exists. A simple textbox where I must know the ID of the associated record is not sufficient.
I would hope that the resulting code would look like:
# in app/models/my_resource.rb
class MyResource
belongs_to :user
end
# in app/admin/my_resource.rb
form do |f|
f.inputs 'Details' do
f.input :user, as: :something_convenient, plus_optional: :parameters_if_needed
end
f.actions
end
The thing you are looking for is chosen-rails gem.
Some time ago I've answered few questions on the topic, so not to repeat stuff please see them for more details:
Answer 1
Answer 2
I am writing a Spree extension to allow certain items in the cart/order to be linked to each other.
A "setting" product can be associated with a "center stone" product. Eventually, there shall be constraints enforcing which things can reference each other, but that's not important yet.
Here's how I've changed the LineItem to include self references:
Spree::LineItem.class_eval do
has_one :center_stone, class_name: "LineItem", foreign_key: "setting_id"
belongs_to :setting, class_name: "LineItem"
end
...and the corresponding DB migration:
class AddSettingRefToLineItems < ActiveRecord::Migration
def change
add_reference :spree_line_items, :setting, index: true, foreign_key: true
end
end
What I need to accomplish next is to modify the "Add to Cart" form on the product page so that an item being added to the cart can get associated with an item that is already in the cart. How do I do this?
Use Case Example
Product A and Product B are both in my cart. I am looking at the page for Product C. I want to see the options:
Add to Product A
Add to Product B
Add to Cart Alone
Clicking any of these options creates a Spree::LineItem for Product C as usual. If click the first two option, I also want the LineItem for Product C's setting_id to reference the LineItem for Product A in my cart.
As was discovered, the main questions is: how to customize Spree's "add to card" function.
You need to customize views:
Deface::Override.new(:virtual_path => 'spree/products/_cart_form',
:name => 'centerproduct_cart_form',
:replace => "<what to replace>",
:erb => "<with what to replace>")
This should go to your app/overrides/centerproduct_cart_form.rb file (you can change name of file, just make sure name parameter in the code sample above will be changed as well to the same value).
You can figure out what to replace and with what to replace parts by looking at the source code of the view:
https://github.com/spree/spree/blob/master/frontend/app/views/spree/products/_cart_form.html.erb
I have a migration and model with a table called medications. I need to pick a specific row from the medications table. I also am trying to filter out all medications that don't have the current user's id.
Here is the current code I have.
Medication.find(:name, :conditions => { :user_id => current_user.id }, :order => "Medication.name")
I know this isn't complete, but any help would be greatly appreciated.
You can load the first medication for a specific user_id like this (assuming that your medications table has an user_id):
Medication.where(user_id: current_user.id).order(:name).first
When our User model has a belongs_to :medications it can be simplified to:
current_user.medications.order(:name).first
When you want to load the e.g. 5th medication just add an offset of 4:
current_user.medications.order(:name).offest(4).first
Or load all medications and iterate through them:
current_user.medications.limit(10).each do |medication|
puts medication.name
end
When you want to output the first ten medications on a website you would do something like this:
# in the controller
#medications = current_user.medications.order(:name).limit(10)
# in the view
<ul>
<% #medications.each do |medication| %>
<li><%= medication.name %></li>
< end %>
</ul>
The finder syntax you use is deprecated and was replaced in Rails 4. See Rails Guide about querying the database.
This is a perfect use case for a has_many :through association if you don't already have it set up.
class User < ActiveRecord::Base
has_many :prescriptions # or whatever
has_many :medications, :through => :prescriptions
end
class Prescription < ActiveRecord::Base
belongs_to :user
belongs_to :medication
end
class Medication < ActiveRecord::Base
has_many :prescriptions
has_many :users, :through => :prescriptions
end
Now you can do stuff like #user.medications to retrieve only that user's medications, #user.medications.find(params[:medication_id] to find a specific one within a user's assigned medications, and #user.medications << Medication.find_by(name: 'Aspirin') to add a medication to a user, and so on.
This is a basic overview of this technique, but it's a basic Rails concept so there's plenty of information on use cases close to whatever you may be trying to do.
I fixed the problem and I have decided to post the answer in case anybody else seems to have a similar problem.
I ended up not putting anything in my controller or adding anything new to my models. I just used this line of code in the view.
<%= Medication.offset(0).where(:user_id => current_user.id).pluck(:name).first %>
I couldn't have done it without the support of everyone who posted, Thank you!
I am new to Rails and just building my first app (coming from a PHP and .NET background and loving it btw) but I have run into a problem that I am struggling to find an answer to, even though I am sure there is an easy one!!
My project has 3 main Models; Locations, Services and Location Services
There are multiple services available and a Location can have any number of them. Basically I am using a record in Locations Services to store the ID of the selected service and the ID of the location.
A simplified version of my models are as below:
class Location < ActiveRecord::Base
has_many :location_services
end
class Service < ActiveRecord::Base
has_many :location_services
end
class LocationService < ActiveRecord::Base
belongs_to :location
belongs_to :service
end
I have read up about nested forms and using 'accepts_nested_attributes_for' to allow sub forms to edit data taken from another model which sounds very similar to what I want, except I don't want to just be able to edit the Location Services that I have, I want to be able to choose from every single available Service as checkboxes, then when checked and my Location is saved, I want it to create a record for each selected service in the Location Services table using the ID of the Location and the ID of the service
Im sure I could easily generate all the tickboxes with Services.all and then loop through that and then in my controller grab all of the ticked checkboxes from the POST, loop through them and build an array of all of them and then pass that array to Location.location_services.create([]) but this is rails and I feel like there is probably a better way to do it?
So firstly, am i going about this in a stupid way? Rather than having 3 tables, is there a better way of doing it? And is there a nice way of generating and saving all of the services?
Many thanks in advance
David
Many thanks Yan for your help on this one, I have finally managed to resolve my issue and it actually turned out to be really simple. I am posting here in the hope it helps someone else.
What I needed to do was add a has_many relation to services through location services so my model now looks like below:
class Location < ActiveRecord::Base
has_many :services, :through => :location_services
has_many :location_services
accepts_nested_attributes_for :location_services
end
I updated my view to include:
<%= f.collection_check_boxes(:service_ids, Service.all, :id, :name) do |b| %>
<%= b.label(class: "check_box") do %>
<%= b.check_box %>
<%= b.object.name %>
<% end %>
<% end %>
Then in my controller I have:
def location_params
params.require(:location).permit(:service_ids => [])
end
I have stripped out all of my other fields for simplicity. Then finally in the Update method, it is as simple as:
def update
#location.update(location_params)
redirect_to #location, notice: 'Location was successfully updated.'
end
Hope this helps someone out!!
Many thanks
David
A has_many relationship adds a number of methods to your model. From which you only need the collection_singular_ids method, which does the following:
Replace the collection with the objects identified by the primary keys
in ids. This method loads the models and calls collection=.
The above method can be combined with collection_check_boxes as explained in this tutorial. So in your case you'll have something like:
f.collection_check_boxes :location_service_ids, LocationService.all, :id, :name
Note that the last parameter (:name here) is the text_method_option which generates the labels for your check boxes.
Last but not least: don't forget to use accepts_nested_attributes properly.