polymorphic association with formtastic and mongo - ruby-on-rails-3

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.

Related

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

How to implement has_many :through relationship in rails with this example

i've been searching through similar questions but i still don't get how implement this relationship. I have of course three models :
class Recetum < ActiveRecord::Base
attr_accessible :name, :desc, :duration, :prep, :photo, :topic_id
has_many :manifests
has_many :ingredients, :through => :manifests
end
class Ingredient < ActiveRecord::Base
attr_accessible :kcal, :name, :use, :unity
has_many :manifests
has_many :recetum, :through => :manifests
end
class Manifest < ActiveRecord::Base
attr_accessible :ingredient_id, :quantity, :receta_id
belongs_to :recetum
accepts_nested_attributes_for :ingredient
belongs_to :ingredient
end
Recetum would be a recipe (typo when scaffolding), this recipe may have one or more ingredients (already on the db). So when i create a new Recetum, i need the new recetum to be created and one record inserted in manifest for each ingredient entered by the user.
I would need some help now with views and controllers, how do i create the form for recetum with fields for the ingredients and more important what do i have to modify recetum controller.
Any suggestions or help would be very much appreciated as this part is crucial for my project, thanks in advance.
You have a couple options, and mainly they depend on what you want to do in your view. Do you want to display a set number of max_ingredients or do you want it to be completely dynamic? The dynamic case looks better for the user for sure, but it does make for some more complicated code.
Here is a good RailsCast which explains how to do it dynamically via JavaScript:
http://railscasts.com/episodes/74-complex-forms-part-2
Unfortunately, not everyone runs with JavaScript enabled so you may want to consider doing it the static way.
Firstly, I don't think you need accepts_nested_attributes_for in your Manifest model. However, I do think you need it in your Recetum model. If you're going the static route, you'll probably want to set a reject_if option too.
accepts_nested_attributes_for :manifests, reject_if: :all_blank
Once you do this, you'll need to add manifests_attributes to your attr_accessible.
With the static route, you'll need to prebuild some of the manifests. In your new controller you'll want something like this:
max_ingredients.times do
#recetum.manifests.build
end
In your edit and the error paths of your create and update, you may want:
(max_ingredients - #recetum.manifests.count).times do
#recetum.manifests.build
end
Finally, your view will need some way to set the ingredient. I'll assume a select box for now.
f.fields_for :manifests do |mf|
mf.label :ingredient_id, "Ingredient"
mf.collection_select :ingredient_id, Ingredient.all, :id, :name
You'll want to add some sort of formatting through a list or table probably.
Hopefully, that's enough to get you started.

Active record::relation instead of the object's class, when using where method with Rails 3

I am trying to get the attributes of the objects after calling a .where query. The query is the following:
#matchers = TutoringSession.where(:begin_time_hour => 21).limit(5)
I get an array of tutoring sessions as a result. But I would like to be able to return only specific attributes of each of the matching tutoring sessions. So I have the following code in my view:
#matchers.each do |matcher|
matcher.begin_time_hour
end
Instead of listing each of matcher's begin_time_hour attributes, it all of the attributes for each matcher object. I have experimented with this block trying "puts matchers.begin_time_hour," and have also tried using partials to solve this problem, however I keep running into issues. If I ask #matcher.class, it says, it is ActiveRecord::Relation object. I thought it would be a TutoringSession object.
Here are my models, in case this helps.
require 'date'
class TutoringSession < ActiveRecord::Base
belongs_to :refugee
belongs_to :student
before_save :set_day_and_time_available, :set_time_available_hour_and_day
attr_accessor :begin_time, :book_level, :time_open
attr_accessible :time_open, :day_open, :tutoring_sessions_attributes, :page_begin, :begin_time
end
and my other class is the following
require 'date'
require 'digest'
class Refugee < ActiveRecord::Base
has_many :tutoring_sessions
has_many :students, :through => :tutoring_sessions
accepts_nested_attributes_for :tutoring_sessions, :allow_destroy => true
attr_accessible :name, :email, :cell_number, :password, :password_confirmation, :day_open, :time_open, :tutoring_sessions_attributes
end
Please let me know if you need more info. Thanks for the help!
It looks like you're not outputting anything to the view. By calling
#matchers.each do |matcher|
matcher.begin_time_hour
end
you get the result from running the loop, which is the relation, instead of the data. You are accessing begin_time_hour, but you aren't doing anything with it. You'd need something more like this to display the begin_time_hour fields.
<% #matcher.each do |matcher| %>
<%= matcher.begin_time_hour %>
<% end %>
By the way, #matchers should be an ActiveRecord::Relation object, a representation of the sql query that will be generated from the where and limit clauses. Calling all on the relation with make it an array of TutoringSession objects
#matchers = TutoringSession.where(:begin_time_hour => 21).limit(5).all
Calling each implicitly runs the query and iterates over the TutoringSession objects, so you shouldn't need to worry about that though.

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

Filter based on model attribute has_many relationship through, rails 3?

I have a simple question, but can't seem to find any solution, though I have found things that are similar, but just not exactly what I am looking for.
I have an application where a User has many Assets through the class UserAsset. I want to be able to do current_user.user_assets , but I only want to return records that have an Asset with a specified field value of "active".
This post is similar but I need to use the main model not the join model as a filter.
class UserAsset < ActiveRecord::Base
belongs_to :asset
belongs_to :user
end
class Asset < ActiveRecord::Base
has_many :user_assets
has_many :users, :through => :user_assets
end
class User < ActiveRecord::Base
has_many :user_assets
has_many :assets, :through => :user_assets
end
I tried setting the default scope on Asset, and also some conditions on the has many (user_assets) relationship, but rails is failing to consider the join on the Assets table. ie Unknown column 'asset.live' in 'where clause'. Trying to achieve the following:
#active_user_assets = current_user.user_assets #only where assets.active = true
So how do I use conditions or scopes to achieve this? I need the user_asset object because it contains info about the relationship that is relevant.
Thanks in advance!
You want current_user.assets, then your scopes should work.
Oh, but you want the user_assets. Hmm. I think you need the :include clause to find() but where to put it, I can't be arsed to think of right now.
Perhaps
current_user.user_assets.find(:all, :include => :assets).where('asset.live=?', true)
(I'm not on Rails 3 yet, so that's going to be mangled)
Are you using :through when you really want a HABTM?