I'm using activeadmin to manage the models of my rails app. I have a User model that uses the can can separate role model and those roles are modelled with inheritance and use STI on ActiveRecord.
The problem is that no matter on which of the roles' activeadmin controller page I'm, the index populated shows all the instances of Role the subclasses
Example:
I create RoleA and RoleB instances. Then I go to the RoleA index page and RoleB is shown in the list. The opposite happens as well.
The Details
I have several different roles which follow the role-object pattern where I have an abstract role and it's subclasses. I use this pattern because one User can have more than one role.
On the other side, Roles share basic attributes but differ in some of them, that's why inheritance is use to model those roles
ROLE
|
---> RoleA
|
---> RoleB
|
---> RoleC
I have this migration for the STI
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name #this is the name I want the role to show up on screen
t.references :role_a_attr
t.references :role_b_attr
t.string :type
t.timestamps
end
end
end
In my activeadmin controllers I have registered: Role, RoleA, RoleB and RoleC.
Role
ActiveAdmin.register Role do
config.clear_action_items! # We don't want to create this kind of objects directly
index do
column :id
column :name
default_actions
end
end
RoleA
ActiveAdmin.register RoleA do
#we only want one super admin role
config.clear_action_items! if RoleA.first
menu :parent => 'Roles'
show do
attributes_table do
row :id
row :name
row :created_at
row :updated_at
end
end
end
RoleB
ActiveAdmin.register RoleB do
menu :parent => 'Roles'
end
RoleC
ActiveAdmin.register RoleC do
menu :parent => 'Roles'
end
What am I doing wrong?
Apparently ActiveAdmin does not like the default setting.
Rails documentation advices to change the default name anyway, so I did by adding this to my migration file
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
# some other attributes
t.string :object_type #this will be your 'type' column from now on
t.timestamps
end
add_index :roles, :object_type
end
end
Then on the role class I added
set_inheritance_column 'object_type'
Surprisingly, this change was not getting any effect after doing a rake db:migrate. So I did a db:drop, db:reset, db:migrate and db:seed and everything started working fine.
Side note: Bare in mind that if you are doing a 'big bang' approach on development (you shouldn't) you can lock yourself out of the app when roles start working.
Related
I am building an eCommerce website using rails 5 and activeadmin gem to manage my dashboard. I have a product and a category model in a many to one relationship.
class Product < ApplicationRecord
before_destroy :not_referenced_by_any_line_item
belongs_to :category
has_many :line_items, dependent: :destroy
has_many :reviews, dependent: :destroy
def self.search(search)
all.where("lower(title) LIKE :search", search: "%#{search}%")
end
private
def not_referenced_by_any_line_item
unless line_items.empty?
errors.add(:base, "line items present")
throw :abort
end
end
end
class Category < ApplicationRecord
has_many :products, dependent: :destroy
def self.search(search)
all.where("lower(category_name) LIKE :search", search: "%#{search}%")
end
end
I then registered the models to the activeadmin dashboard as below
ActiveAdmin.register Product do
permit_params :title, :description, :availability,
:price, :photo_link, :category_id, :advert, :pictureOne,
:pictureTwo, :pictureThree
end
ActiveAdmin.register Category do
permit_params :category_name, :photos
end
I can now select a product category on the project form when creating a product but the problem is, instead of a category name or any other field to display on the project category form input field so that you know exactly which category you are selecting,an abject is being displayed making it difficult to know which category you are selecting. display of dropdown of product category input form field:
ActiveAdmin's default functionality is to look for a name field on a given model when deciding what to render as the record's identifier. If the model doesn't have a name field, ActiveAdmin doesn't how else to let you which record you're dealing with besides being able to show you a stringified mess of the location where that record is in memory (It's the same string you'd get if you did Category.first.to_s in the console).
To get the ActiveAdmin to recognize the name, you have to override the default edit form it creates for you so you can customize the select label.
You'll add all the fields you want to be editable to the form. When you get adding the input for the category, you specify that you want that field to be a select and you can customize the select's label, like so:
# app/admin/product.rb
ActiveAdmin.register Product do
permit_params :title, :description, :availability,
:price, :photo_link, :category_id, :advert, :pictureOne,
:pictureTwo, :pictureThree
form do |f|
f.inputs do
# Add a form input for the category
#
# This approach also allows you to specify which categories you
# allow to be selected, in the "collection" attribute.
#
# Inside the "map" call, you have the proc return an array with the first item
# in the array being the name of the category (the label for the select)
# and the second item being the category's ID (the select's value)
f.input :category_id, label: 'Category', as: :select, collection: Category.all.map{ |c| [c.category_name, c.id]}
# Then add other inputs
f.input :title
f.input :description
f.input :availability
# ...
# (Add f.input for the rest of your fields)
end
f.actions
end
end
You'll follow similar methods when you need to render the name a category in other places in ActiveAdmin.
If it's not too much trouble, you'll probably be better off renaming category_name on your Category model to just name. That way, you'll be fighting with ActiveAdmin a lot less and won't need to make customizations like this as much.
I want to use FriendlyId to achieve this localhost3000/users/edu/profile but I do not know how to do it!
I have these models User and UserProfile
class User < ActiveRecord::Base
has_one :user_profile, :dependent => :destroy
extend FriendlyId
friendly_id :name, :use => :slugged
end
class UserProfile < ActiveRecord::Base
attr_accessible :user_id, :name, :surname, :nickname
belongs_to :user
end
How do I load in name of User the name of UserProfile? and
How do you update name of User when the name of UserProfile changes?
For the first I used
class User < ActiveRecord::Base
...
def name
if user_profile
"#{user_profile.name}"
end
end
But I can't make it change when I update or create a new Profile in UserProfile.
Using Ruby 1.9.3 and Rails 3.2.13.
If I understood it correctly, your problem is about sharing data between models, not about FriendlyId.
It seems delegate is your best bet here. It's a method in ActiveSupport that allows one model to expose another model's methods as their own.
class User < ActiveRecord::Base
delegate :name, :name=, :to => :user_profile
end
Reference: http://api.rubyonrails.org/classes/Module.html#method-i-delegate
The reason to delegate both :name and :name= is that the former method allows you to read from that attribute (getter), while the latter allows you to write to it (setter).
Before making these changes you'll want to run a migration to remove the name field from the users table in the database, since from now on you'll be using the data in the other model.
rails g migration remove_name_from_users name:string
I'm building an app where a User can create a Group and then invite other Users to join his group. A User can join many Groups and a Group will include many Users. I'm confused as to how I can add a specified user to a specified group. For example I have a User object who's id is 2. How do I take this User object and add it to a specified group? I'm able to create a group with User.first.groups.create but I'm unable to do the reverse.
For example I tried: Group.first.create(user_id: 1) to add User 1 to Group 1. Rails didn't like that.
I have the following database design:
class User < ActiveRecord::Base
attr_accessible :name, :provider, :uid
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
attr_accessible :name, :share_link
has_and_belongs_to_many :users
end
I also created a join table with the following migration:
class GroupsUsers < ActiveRecord::Migration
def up
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
end
def down
drop_table :groups_users
end
end
Thanks in advance for all your help!
user = User.find(2) # this is user object you say you have already
group.users << user
group.save
group is the specific group you are adding to (in your quick example, it'd be Group.first.
I am building an app where users can create recipes, see all recipes created, view their own recipes in a member area and finally i would like for users to add "favorites" to their account.
I am new to Rails but have read through docs and this is my understanding of what it should look like in the backend. Could someone confirm that this looks correct or advise of any errors please, with explanations if I have done something wrong (which is probably the case)?
So this is my code:
User Model
has_many :recipes
has_many_favorites, :through => :recipes
Recipe Model
belongs_to :user
has_many :ingredients #created seperate db for ingredients
has_many :prepererations #created seperate db for prep steps
Favorite Model
belongs_to :user
has_many :recipes, :through => :user
#this model has one column for the FK, :user_id
Favorites Controller
def create
#favrecipes =current_user.favorites.create(params[:user_id])
end
I then wanted to have a button to post to the db, so I have this:
<%= button_to("Add to Favorites" :action => "create", :controller => "favorites" %>
I think I am probably missing something in my routes but I am unsure.
The particular setup you describe mixes several types of associations.
A) User and Recipe
First we have a User model and second a Recipe model. Each recipe belonging to one user, hence we have a User :has_many recipes, Recipe belongs_to :user association. This relationship is stored in the recipe's user_id field.
$ rails g model Recipe user_id:integer ...
$ rails g model User ...
class Recipe < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :recipes
end
B) FavoriteRecipe
Next we need to decide on how to implement the story that a user should be able to mark favorite recipes.
This can be done by using a join model - let's call it FavoriteRecipe - with the columns :user_id and :recipe_id. The association we're building here is a has_many :through association.
A User
- has_many :favorite_recipes
- has_many :favorites, through: :favorite_recipes, source: :recipe
A Recipe
- has_many :favorite_recipes
- has_many :favorited_by, through: :favorite_recipes, source: :user
# returns the users that favorite a recipe
Adding this favorites has_many :through association to the models, we get our final results.
$ rails g model FavoriteRecipe recipe_id:integer user_id:integer
# Join model connecting user and favorites
class FavoriteRecipe < ActiveRecord::Base
belongs_to :recipe
belongs_to :user
end
---
class User < ActiveRecord::Base
has_many :recipes
# Favorite recipes of user
has_many :favorite_recipes # just the 'relationships'
has_many :favorites, through: :favorite_recipes, source: :recipe # the actual recipes a user favorites
end
class Recipe < ActiveRecord::Base
belongs_to :user
# Favorited by users
has_many :favorite_recipes # just the 'relationships'
has_many :favorited_by, through: :favorite_recipes, source: :user # the actual users favoriting a recipe
end
C) Interacting with the associations
##
# Association "A"
# Find recipes the current_user created
current_user.recipes
# Create recipe for current_user
current_user.recipes.create!(...)
# Load user that created a recipe
#recipe = Recipe.find(1)
#recipe.user
##
# Association "B"
# Find favorites for current_user
current_user.favorites
# Find which users favorite #recipe
#recipe = Recipe.find(1)
#recipe.favorited_by # Retrieves users that have favorited this recipe
# Add an existing recipe to current_user's favorites
#recipe = Recipe.find(1)
current_user.favorites << #recipe
# Remove a recipe from current_user's favorites
#recipe = Recipe.find(1)
current_user.favorites.delete(#recipe) # (Validate)
D) Controller Actions
There may be several approaches on how to implement Controller actions and routing. I quite like the one by Ryan Bates shown in Railscast #364 on the ActiveRecord Reputation System. The part of a solution described below is structured along the lines of the voting up and down mechanism there.
In our Routes file we add a member route on recipes called favorite. It should respond to post requests. This will add a favorite_recipe_path(#recipe) url helper for our view.
# config/routes.rb
resources :recipes do
put :favorite, on: :member
end
In our RecipesController we can now add the corresponding favorite action. In there we need to determine what the user wants to do, favoriting or unfavoriting. For this a request parameter called e.g. type can be introduced, that we'll have to pass into our link helper later too.
class RecipesController < ...
# Add and remove favorite recipes
# for current_user
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << #recipe
redirect_to :back, notice: 'You favorited #{#recipe.name}'
elsif type == "unfavorite"
current_user.favorites.delete(#recipe)
redirect_to :back, notice: 'Unfavorited #{#recipe.name}'
else
# Type missing, nothing happens
redirect_to :back, notice: 'Nothing happened.'
end
end
end
In your view you can then add the respective links to favoriting and unfavoriting recipes.
<% if current_user %>
<%= link_to "favorite", favorite_recipe_path(#recipe, type: "favorite"), method: :put %>
<%= link_to "unfavorite", favorite_recipe_path(#recipe, type: "unfavorite"), method: :put %>
<% end %>
That's it. If a user clicks on the "favorite" link next to a recipe, this recipe is added to the current_user's favorites.
The Rails guides on associations are pretty comprehensives and will help you a lot when getting started.
Thanks for the guide, Thomas! It works great.
Just wanted to add that in order for your favorite method to work correctly you need to wrap the text in double quotes instead of single quotes for the string interpolation to function.
redirect_to :back, notice: 'You favorited #{#recipe.name}'
->
redirect_to :back, notice: "You favorited #{#recipe.name}"
https://rubymonk.com/learning/books/1-ruby-primer/chapters/5-strings/lessons/31-string-basics
This thread was super helpful!!
Thank you!
Don't forget to include the = in the form tags.
<% if current_user %>
<%=link_to "favorite", favorite_recipe_path(#recipe, type: "favorite"), method: :put %>
<%=link_to "unfavorite", favorite_recipe_path(#recipe, type: "unfavorite"), method: :put %>
<% end %>
The selected answer is really good however I can't post a comment and I really do have an question about above. How can you limit a user to have one favourite recipe? With the above answer a user can continue pressing favorite and many entries will be created in the database...
Ok, so I thought I understood how the relationship specifications work in rails but I've been struggling with this for a day now.
Some context, I have two models Cars & Model Names (e.g. Impala, Charger, etc), where Cars are instances of Model Names, and Model Names really is nothing more than a lookup table of Model Names and some other model level attributes. The Model Name controller is nested within the admin namespace as only admins can CRUD Model Names. End users can add instances of cars to the Cars model.
So, in routes.rb I have:
resources :cars
namespace :admin do resources :model_names end
The Model's are defined as:
class Admin::ModelName < ActiveRecord::Base
end
class Car < ActiveRecord::Base
belongs_to :admin_model_name
end
The Migrations are:
class CreateCars < ActiveRecord::Migration
def self.up
create_table :cars do |t|
t.string :chassis_number
t.string :description
t.references :admin_model_name
t.timestamps
end
end
class CreateAdminModelNames < ActiveRecord::Migration
def self.up
create_table :admin_model_names do |t|
t.string :model
t.integer :sort_index
#...additional attributes removed
t.timestamps
end
The admin CRUD of ModelName all work great. The problem is in the Car views. I think I should be referencing a particular cars model name like such:
<%= #car.admin_model_names.Model =>
But I get the error:
undefined method `admin_model_names' for #<Car:0x000001040e2478>
I've tried the attr_accessible on the ModelNames model but to no avail. The underlying data is referenced correctly. I have also have HABTMT relationship between Cars & Users and that all worked fine referencing each others attributes from the different entities views. But haven't been able to get this to work. Is it due to the nested resource & admin control inheritance?
The only referencing method I found that works is:
<%= Admin::ModelName.find(#car.admin_model_name_id).model %>
But this really seems to be too much code (and expense of a find) to get to that attribute. Is there a Rails way?
Thanks in advance.
Scott
Have you tried:
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => "Admin::ModelName"
end
as stated in
http://guides.rubyonrails.org/association_basics.html
section 3.4?
you may also need to set the :foreign_key => "admin_model_name_id" attribute to specify the referencing model.
Hope it helps.
Did you try
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => 'Admin::ModelName'
end
and if necessary add :foreign_key => '' and add this column to your cars table.