I'm building a web API with Ruby and Grape. I have two classes that requires each other which leads to a situation where I get uninitialized constant class errors. The place where I get the error is in the Entity class for Connector, see the example code below, which requires Card::Entity before it has been inintialized. Is there any way to solve this probelm without moving the Entity definitions to another file?
#card.rb
require_relative 'connector'
require_relative 'caption'
class Card < ActiveRecord::Base
belongs_to :medium
belongs_to :storyline
has_many :connectors, autosave: true
has_many :connected_cards, class_name: "Connector", foreign_key: "connected_card_id"
has_many :captions
accepts_nested_attributes_for :connectors, :captions
class Entity < Grape::Entity
expose :id, documentation: { readonly: true }
expose :cardtype
expose :connectors, using: Connector::Entity
expose :captions, using: Caption::Entity
end
end
#connector.rb
require_relative 'card'
class Connector < ActiveRecord::Base
has_one :card
has_one :connected_card, :class_name => "Card", :foreign_key => "connected_card_id"
class Entity < Grape::Entity
expose :id, documentation: { readonly: true }
expose :title
expose :card, using: Card::Entity
expose :connected_card, using: Card::Entity
end
end
I don't know a lot about grape, but this could be solved by "pre declaring" the class:
#card.rb
require_relative 'caption'
class Connector < ActiveRecord::Base
# empty declaration just to make the import works
end
class Card < ActiveRecord::Base
belongs_to :medium
belongs_to :storyline
has_many :connectors, autosave: true
has_many :connected_cards, class_name: "Connector", foreign_key: "connected_card_id"
has_many :captions
accepts_nested_attributes_for :connectors, :captions
...
end
Still, I think that QPaysTaxes may have a valid point about design here.
Related
class Book
belongs_to :library
end
class Library
belongs_to :city
has_many :books
end
class City
has_many :libraries
has_many :books, through: :library
end
I want to be able to query
Book.where("library.city.name = ?", "Alexandria")
How do I correctly do this with ActiveRecord?
Your city model already has many books through libraries, so I believe
City.find_by(name: "Alexandria").books
should do the trick
I would like to override a Lib Model in my Models and add a relation.
What is the best way to do it ?
Example of a model in rpush lib:
https://github.com/rpush/rpush/blob/f82cc6a25861612ce118b2661f5a47bceb7ebd86/lib/rpush/client/active_record/app.rb
module Rpush
module Client
module ActiveRecord
class App < ::ActiveRecord::Base
self.table_name = 'rpush_apps'
if Rpush.attr_accessible_available?
attr_accessible :name, :environment, :certificate, :password, :connections, :auth_key, :client_id, :client_secret
end
has_many :notifications, class_name: 'Rpush::Client::ActiveRecord::Notification', dependent: :destroy
validates :name, presence: true, uniqueness: { scope: [:type, :environment] }
end
end
end
end
I would like to add a has_many relation without editing the gem
So I thought creating a models/app.rb with this would be a start:
class Rpush::Client::ActiveRecord::App
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
I tried this but nothing changed. Maybe my models/app.rb is not called ?:
module Rpush
module Client
module ActiveRecord
module App
def self.included(includer)
includer.class_eval do
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
end
end
end
end
end
How should I do it ? Is there a way to extend a lib model without removing the original behavior ?
Thanks !
EDIT
I Made it work but only by putting this code directly in config/initializers/rpush.rb
It wasn't working in models/app.rb
class Rpush::Client::ActiveRecord::App
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
If someone has a nicer idea, I'll take it !
Extend the class with class << self
class Rpush::Client::ActiveRecord::App
class << self
[your methods here]
end
end
In the following "department store pattern" I have three models:
class Store
has_many :items, inverse_of: :store, autosave: true
has_many :departments, inverse_of: :store, autosave: true
accepts_nested_attributes_for :departments, allow_destroy: true
class Department
belongs_to :store, inverse_of: :departments
has_many :items, autosave: true, inverse_of: department
accepts_nested_attributes_for :items, allow_destroy: true
class Item
belongs_to :store, inverse_of: :items
belongs_to :department, inverse_of: :items
When I try the following:
store = Store.new
department = store.departments.build
item = department.items.build
store.save
Then the item does not associate with the store.
My solution to the problem was to add the following to the Item model:
class Item
before_validation :capture_store_info
def capture_store_info
self.store = self.department.store
end
I added it to the before_validation callback because in my non-trivial code I have a bunch of validations, including one that checks for the presence of the store model.
Question: My solution works, but is it the correct (ie. Rails conventional) way of solving this problem? Is there a better solution. This feels kinda dirty, and every time I have done something in Rails that felt "kinda dirty" it has come back to bite me later.
Thanks,
JB
Given
User:
class User < ActiveRecord::Base
has_many :discussions
has_many :posts
end
Discussions:
class Discussion < ActiveRecord::Base
belongs_to :user
has_many :posts
end
Posts:
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
I am currently initializing Posts in the controller via
#post = current_user.posts.build(params[:post])
My question is, how do I set/save/edit the #post model such that the relationship between the post and the discussion is also set?
Save and edit discussions along with post
Existing Discussion
To associate the post you're building with an existing discussion, just merge the id into the post params
#post = current_user.posts.build(
params[:post].merge(
:discussion_id => existing_discussion.id
)
You will have to have a hidden input for discussion id in the form for #post so the association gets saved.
New Discussion
If you want to build a new discussion along with every post and manage its attributes via the form, use accepts_nested_attributes
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
accepts_nested_attributes_for :discussion
end
You then have to build the discussion in the controller with build_discussion after you built the post
#post.build_discussion
And in your form, you can include nested fields for discussions
form_for #post do |f|
f.fields_for :discussion do |df|
...etc
This will create a discussion along with the post. For more on nested attributes, watch this excellent railscast
Better Relations
Furthermore, you can use the :through option of the has_many association for a more consistent relational setup:
class User < ActiveRecord::Base
has_many :posts
has_many :discussions, :through => :posts, :source => :discussion
end
class Discussion < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
Like this, the relation of the user to the discussion is maintained only in the Post model, and not in two places.
I have belongs_to :provider in my model, and provider_id is set, but association is still blank:
irb(main):095:0> o2.provider_id
=> BSON::ObjectId('4e0472f36d40ec0004000001')
irb(main):096:0> o2.provider
=> nil
Also, model has provider_id= method with "write_attribute(:provider_id, provider_id)" inside.
What could be wrong here?
Mongoid version is 2.4.0
Thank you!
I believe you need a has_one or has_many association in your other model.
class Foo
include Mongoid::Document
belongs_to :bar
end
class Bar
include Mongoid::Document
has_one :foo
end