Rails 3.1, internalization of values from a habtm relationship? - ruby-on-rails-3

I got a Event model that HABTM Categories. The relationship works fine and I can insert/retrieve values from Categories with no problem.
My questions is, is there a way to interzationalize(I18n) the values of this categories.
Category Model
class Category < ActiveRecord::Base
has_and_belongs_to_many :events
end
Event Model
class Event < ActiveRecord::Base
....
has_and_belongs_to_many :categories
....
_form.html.haml (for events)
- Category.all.each do |category|
.field
= check_box_tag "category_ids[]", category.id, #event.category_ids.include?(category.id)
= category.name

I'm assuming the categories are pretty much fixed (otherwise you wouldn't really be able to do any i18n on them)
One solution would be to save the categories in the database as keys (with underscores) and for each key add the i18n to your locale files:
en.yml
categories:
some_category: "Some category text"
some_other_category: "Some other category text"
......
And if you do for example Category.all.map(&:name) will result in ["some_category", "some_other_category", ....]
And in your view:
- Category.all.each do |category|
.field
= check_box_tag "category_ids[]", category.id, #event.category_ids.include (category.id)
= I18n.t("categories.#{category.name}")
Note this is not a good solution if you're trying to do this dynamically (if that's the case, you're going to need to store the translations in the database, and this might help)

Related

Is there a way to show an enum from another model into a select picker?

I'm a little bit new to Ror. I have a relation one to one between a model 'VotationType' and 'Question'. This relation have to be polymorphical. Now i need a way that allows me to show a select picker in the question/new.html.erb that in the dropdown lists the enum_types for selecting one of them.
Model votation_type.rb
class VotationType < ApplicationRecord
belongs_to :questionable, polymorphic: true
enum enum_type: %i[unique prioritized open_positive answer_couple answer_set]
Model quiestion.rb:
class Question < ApplicationRecord
has_one :votation_type, as: :questionable
The main problem of this is that i can figure it out how can i list the enum_type in the view from another model.
Found a method that works for the moment:
<%= f.select :votation_type, options_for_select(VotationType.enum_types) %>

Returning Columns from Associated Table in Model.all

Summary:
I have a model with a few "belongs_to" associations and when I call Model.all (or another custom method if need be) I want to not only return all columns of Model, but also one column from each of the associated Models. Basically instead of just returning the ID's of the associated Models I want to get a more friendly attribute like "name".
Using Rails 3.2.x
Model Details:
I have five models, basically for data normalization.
class ActionItem < ActiveRecord::Base
belongs_to :action_item_status
belongs_to :prod_ops_acceptance
belongs_to :action_item_priority
belongs_to :incident_ticket
<truncated the rest>
end
class IncidentTicket < ActiveRecord::Base
attr_accessible :number
has_many :action_items
validates_presence_of :number
end
class ActionItemPriority < ActiveRecord::Base
attr_accessible :name
has_many :action_items
validates_presence_of :name
end
class ActionItemStatus < ActiveRecord::Base
attr_accessible :name
has_many :action_items
validates_presence_of :name
end
class ProdOpsAcceptance < ActiveRecord::Base
attr_accessible :name
has_many :action_items
validates_presence_of :name
end
Attempted Solutions:
I've tried many combinations of things including using ActionItem.includes and ActionItem.joins to no avail. The latest thing I tried is (trying only for the 'number' attribute of the IncidentTicket model to start with...)
ActionItem.all(
select: 'action_items.title, incident_tickets.number',
joins: 'INNER JOIN incident_tickets
ON action_items.incident_ticket_id = incident_tickets.id')
The above only returns the 'title' attribute from the ActionItem model and not the 'number' attribute from the IncidentTicket model despite the SQL looking correct. It seems like the SELECT on the joined table is completely ignored no matter what I try.
Obviously I am seriously missing something here or doing this completely wrong. I feel like there is some ActiveRecord magic that I'm missing out on that makes this trivial. Any help would be much appreciated! Please let me know if you need more details, I feel like this is kind of difficult to explain...
This ought to work for you:
action_items =
ActionItem.joins(:incident_ticket, :action_item_priority, ...)
.select(%[ action_items.title,
incident_tickets.number AS incident_ticket_number,
action_item_priorities.name AS action_item_priority_name,
... ]
)
.all
logger.info(action_items.first.incident_ticket_number)
What I ended up doing for now is creating a method that returns an array containing the results of ActionItem.all with the additional attributes I want injected in. This can probably be optimized, but I haven't spent any more time focusing on that just yet:
def self.all_with_extras
action_items_with_extras = []
action_items = ActionItem.all.to_json
JSON.parse(action_items).each do |ai|
extras = {
'incident_ticket_number' => IncidentTicket.find(ai['incident_ticket_id']).number,
'status' => ActionItemStatus.find(ai['action_item_status_id']).name,
'priority' => ActionItemPriority.find(ai['action_item_priority_id']).name,
'acceptance' => ProdOpsAcceptance.find(ai['prod_ops_acceptance_id']).name
}
with_extras = ai.merge(extras)
action_items_with_extras.append(with_extras)
end # each
action_items_with_extras
end # def

polymorphic association with formtastic and mongo

I'm trying to create a relationship between two models in rails.
I have a Product and an Offer where an Offer belongs to a Product.
class Product
include Mongoid::Document
include Mongoid::Timestamps
has_many :offers, as: :trigger_product, :class_name => "Offer"
end
class Offer
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :trigger_product, polymorphic: true
accepts_nested_attributes_for :images, :product
end
in formtastic, the field for the trigger product is added as so
<%= f.input :trigger_product, :as=> :select, :multiple => false, :collection => #offer.trigger_products_list %>
when I submit the form, I get an error
NameError in Admin::OffersController#create
uninitialized constant TriggerProduct
app/controllers/admin/Offers_controller.rb:7:in `create'
It appears to me the polymorphic association isn't working, I don't think I should need to create an empty model to hold the TriggerProduct, but the error leads me to believe this is the issue.
Any suggestions here?
Turns out this had to do with a the relationship needing to know of a product_type value, as product is a parent of many product types.
No way anybody here at SO would have gotten that and the Rails error didn't point in the right direction.
If somebody knows how I might have debugged that maybe a way to output all the required fields a relationship is expecting, I'll give you the points.

Modeling inheritance with Ruby/Rails ORMs

I'm trying to model this inheritance for a simple blog system
Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:
simplest entry is an Article that has title and text
Quote, however, does not have a title and has short text
Media has a url and a comment...
etc...
What is a proper way to model this with Ruby on Rails? That is
Should I use ActiverRecord for this or switch to DataMapper?
I would like to avoid the "one big table" approach with lots of empty cells
When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? in the Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id in the postdata table.
EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).
I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.
class Blog < ActiveRecord::Base
# this is your generic association that would return all types of entries
has_many :entries
# you can also add other associations specific to each type.
# through STI, rails is aware that a media_entry is in fact an Entry
# and will do most of the work for you. These will automatically do what cicloon.
# did manually via his methods.
has_many :articles
has_many :quotes
has_many :media
end
class Entry < ActiveRecord::Base
end
class Article < Entry
has_one :article_data
end
class Quote < Entry
has_one :quote_data
end
class Media < Entry
has_one :media_data
end
class ArticleData < ActiveRecord::Base
belongs_to :article # smart enough to know this is actually an entry
end
class QuoteData < ActiveRecord::Base
belongs_to :quote
end
class MediaData < ActiveRecord::Base
belongs_to :media
end
The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:
app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....
and from your views you can do either:
<%= render #blog.entries %> <!-- this will automatically render the appropriate view partial -->
or have more control:
<%= render #blog.quotes %>
<%= render #blog.articles %>
You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a
<%= form_for #entry do |f| %>
<%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %>
type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.
The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.
1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).
2) You'll have to use the becomes() method (#article.becomes(Entry)) to work with the entry as an Entry and not a subclass.
Hope this helps.
Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.
You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:
def Blog > ActiveRecord::Base
has_many :entries
def articles
entries.where('Type =', 'Article')
end
def quotes
entries.where('Type =', 'Quote')
end
def medias
entries.where('Type =', 'Media')
end
end
def Entry > ActiveRecord::Base
belongs_to :blog
end
def Article > Entry
end
def Quote > Entry
end
def Media > Entry
end

Creating a news feed in Rails 3

I want to create an activity feed from recent article and comments in my rails app. They are two different types of activerecord (their table structures are different).
Ideally I would be able to create a mixed array of articles and comments and then show them in reverse chronological order.
So, I can figure out how to get an array of both articles and comments and then merge them together and sort by created_at, but I'm pretty sure that won't work as soon as I start using pagination as well.
Is there any way to create a scope like thing that will create a mixed array?
One of the other problems for me, is that it could be all articles and it could be all comments or some combination in between. So I can't just say I'll take the 15 last articles and the 15 last comments.
Any ideas on how to solve this?
When I've done this before I've managed it by having a denormalised UserActivity model or similar with a belongs_to polymorphic association to an ActivitySource - which can be any of the types of content that you want to display (posts, comments, up votes, likes, whatever...).
Then when any of the entities to be displayed are created, you have an Observer that fires and creates a row in the UserActivity table with a link to the record.
Then to display the list, you just query on UserActivity ordering by created_at descending, and then navigate through the polymorphic activity_source association to get the content data. You'll then need some smarts in your view templates to render comments and posts and whatever else differently though.
E.g. something like...
user_activity.rb:
class UserActivity < ActiveRecord::Base
belongs_to :activity_source, :polymorphic => true
# awesomeness continues here...
end
comment.rb (post/whatever)
class Comment < ActiveRecord::Base
# comment awesomeness here...
end
activity_source_observer.rb
class ActivitySourceObserver < ActiveRecord::Observer
observe :comment, :post
def after_create(activity_source)
UserActivity.create!(
:user => activity_source.user,
:activity_source_id => activity_source.id,
:activity_source_type => activity_source.class.to_s,
:created_at => activity_source.created_at,
:updated_at => activity_source.updated_at)
end
def before_destroy(activity_source)
UserActivity.destroy_all(:activity_source_id => activity_source.id)
end
end
Take a look at this railscast.
Then you can paginate 15 articles and in app/views/articles/index you can do something like this:
- #articles.each do |article|
%tr
%td= article.body
%tr
%td= nested_comments article.comment.descendants.arrange(:order => :created_at, :limit => 15)
This assumes the following relations:
#app/models/article.rb
has_one :comment # dummy root comment
#app/models/comment.rb
belongs_to :article
has_ancestry
And you add comments to an article as follows:
root_comment = #article.build_comment
root_comment.save
new_comment = root_comment.children.new
# add reply to new_comment
new_reply = new_comment.children.new
And so forth.