Annotating Rails models that have Globalize3 translation tables - ruby-on-rails-3

Is it possible to use the annotate (2.4.1.beta) gem to output globalize3 (0.2.0) translated properties in the models that they translate?
If I have a Post creation migration generated like so
class CreatePosts < ActiveRecord::Migration
def up
create_table :posts do |t|
t.timestamps
end
Post.create_translation_table! title: :string, text: :text
end
def down
drop_table :posts
Post.drop_translation_table!
end
end
and its corresponding class looking like
class Post < ActiveRecord::Base
attr_accessible :title, :text
translates :title, :text
end
since the :title and :text attributes are not in the posts table but in the post_translations table, when I run
$ annotate --position before
they are not included in the output for the Post model:
# == Schema Information
#
# Table name: posts
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
#
class Post < ActiveRecord::Base
...
Is there any way to include these attributes without manually typing them in after every annotation generation?

For now, unfortunately the answer to this question is no, there is no way to include Globalize translation attributes in an annotation generation. Will just need to keep monitoring development on annotate's Github repo.

Related

How to select a model field to represent a many to one relationship on active admin dashboard?

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.

Rails Joining together multiple tables

So I am trying to create a news feed of sorts but I am unsure as to how to make the queries.
I have a user model, a model for followed locations and a model for followed users. And then I have a model for comments. I need to grab all the comments from users that a user follows and all comments from a location that a user follows and I have to put these together.
I'm not that familiar with how to do this in sql or rails. Can anyone link me to an article or the docs where I might find how to do this?
If you need more information just comment what else I should include because I was unsure what to include in the post.
The comments model looks like this and it is polymorphic and can be posted to locations and events
create_table :comments do |t|
t.text :text
t.integer :user_id
t.integer :commentable_id
t.string :commentable_type
And then there is two separate tables for following users and following locations
create_table :followed_locations do |t|
t.integer :user_id
t.integer :location_id
create_table :followed_users do |t|
t.integer :user_id
t.integer :followed_id
Here's how the model associations would look:
class User < ActiveRecord::Base
has_many :comments, as: :commentable
has_many :followed_locations
has_many :followed_users
def followed_items
followed_locations.map(&:location).flatten + followed_users.map(&:followed).flatten
end
end
class Location < ActiveRecord::Base
has_many :comments, as: :commentable
end
class FollowedUser < ActiveRecord::Base
belongs_to :user
belongs_to :followed, class_name: 'User'
end
class FollowedLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
The code above defines the relationships among all the models, and adds one User instance method to collect all the items (locations or users) that a given user follows. Now you can gather all the comments for users/locations that a single user is following, like so:
user.followed_items.map(&:comments).flatten
This will gather all the followed items for a user (both locations and other users), get a list of all their comments, then flatten them into a simple array. If you want to sort them, my creation for example, tack that onto the end:
user.followed_items.map(&:comments).flatten.sort_by(&:created_at)
There are ways to optimize this, but at this point you probably just want to focus on getting the concepts down.
UPDATE:
I've created a simple Rails 4 app that implements this solution, and published it on github:
https://github.com/rubycuts/so26169791

Implement "Add to favorites" in Rails 3 & 4

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...

referencing attributes in models with belongs_to relationships through a nested namespace

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.

How do I reference data in an HABTM table without a model in Rails 3?

So I recently created an HABTM relationship between two models (project & user).
Before, I had a user_id column in my project table - that would act like a foreign key. Now there is an entire table that does that.
But how do I reference projects that have a specific user_id & project_id ?
For instance, I used to have a section of my view that looked like this:
<div class="field">
<%= f.label :project_id %><br />
<%= collection_select(:stage, :project_id, Project.where(:user_id => current_user), :id, :name) %>
<br />
</div>
But how do I now pull the same info from the db, with no model for the HABTM table? The new table is called 'projects_users'.
My projects model looks like this:
# == Schema Information
# Schema version: 20101125223049
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255)
# description :string(255)
# notified :boolean
# created_at :datetime
# updated_at :datetime
#
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :stages, :dependent => :destroy
has_many :uploads
has_many :comments
#before_validation { |project| project.user = Authorization.current_user unless project.user }
end
My User Model looks like this:
# == Schema Information
# Schema version: 20101124095341
#
# Table name: users
#
# id :integer not null, primary key
# email :string(255) default(""), not null
# encrypted_password :string(128) default(""), not null
# password_salt :string(255) default(""), not null
# reset_password_token :string(255)
# remember_token :string(255)
# remember_created_at :datetime
# sign_in_count :integer default(0)
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string(255)
# last_sign_in_ip :string(255)
# created_at :datetime
# updated_at :datetime
# username :string(255)
# role :string(255)
#
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, and :lockable
devise :database_authenticatable, :registerable, :timeoutable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_and_belongs_to_many :projects
has_many :stages
has_many :uploads
has_many :comments
has_many :assignments
has_many :roles, :through => :assignments
def role_symbols
roles.map do |role|
role.name.underscore.to_sym
end
end
end
As an aside...how do I edit that table from the rails console - without a model?
Thanks.
I think this will work for you. Basically you're grabbing all the projects for a certain user and then narrowing it down by project_id.
user_id = 3 #example
project_id = 2 #example
User.find(user_id).projects.find(project_id)
If you want to manually edit the join table you can use the relationships directly.
project = Project.create
user = User.first
#add a new row to the join table for the user_id,project_id
user.projects << project
#delete all records from the join table referencing this user.
user.projects = []
I'm not sure I fully understand your question, but Project.where(:user_id => current_user) should become current_user.projects
And to add user with id 1 to project with id 3 you'd do
Project.find(3).users << User.find(1)
Is this what you've been trying to do ?
This RailsCasts helped me a lot - it will answer your questions. http://railscasts.com/episodes/163-self-referential-association