I've two tables which are related say A and B. A has has_many to B and B belongs_to A. However I've a field in A stored say A.account_number. And A is totally unrelated to table C which is accounts table. D has account details like addresses and other details. C has has_many relation to D and D belongs_to C. Now using acts_as_api in A and B . I wrote a big query which almost fetches every field I need except for the account and acoount details. How do I get this details using acts_as_api. I tried using calling scopes sub resource method. but it did not work. Any ideas. Please share. I'm new to rails. Here is my code.
Let's Say
A-> item_people
B-> item_people_roles
C-> people_accounts
D-> people_account_details
Class ItemPeople
class ItemPeople < ActiveRecord::Base
has_many:item_people_roles, class_name => "ItemPeopleRole", :dependent => :destroy
accepts_nested_attributes_for :item_people_roles, :allow_destroy => true, :reject_if => :all_blank
acts_as_api
api_accessible :bill_rewriting do |bill|
bill.add :account_number
bill.add :item_people_roles
end
end
Class ItemPeopleRoles
class ItemPeopleRole < ActiveRecord::Base
attr_accessor :messages
belongs_to :item_people, :class_name => "ItemPeople"
acts_as_api
api_accessible :bill_rewriting do |bill|
bill.add :item_people_id, :as => :shipper_id, :if => lambda{|u|u.role_type_code=="SHIPPER"}
bill.add :item_people_id, :as => :consignee_id, :if => lambda{|u|u.role_type_code=="CONSIGNEE"}
bill.add :item_people_id, :as => :ship_to_id, :if => lambda{|u|u.role_type_code=="SHIPTO"}
end
end
Class C People
class People < ActiveRecord::Base
# This model has account number
# account type fields
end
Class D People_Details
class PeopleDetails < ActiveRecord::Base
# This model has address1, address2, name1, name2
end
Now according to people's role there in the itempeopleroles, I need to get people and people details fields in acts_as_api of ItemPeople. Hope I'm clear now
The best way to accomplish this is via a template Proc/lambda
api_accessible :public do |template|
template.add lambda { |record,options|
# look at options, and use those to build your custom data
return [the correct data]
}, :as => :a_magic_field
end
Then in your controller, get a results set, "annotate" it, and then render it.
raw_results = Model.where(... some where conditions ....)
results = raw_results.as_api_response(:public, ... [some other options to appear in your lambda])
And finally render it:
respond_to do |format|
format.json {render :json => results, :callback => params[:callback]}
format.xml {render :xml => results}
end
With this, you can write any code you want inside your lambda to navigate your data structures, regardless of how it is laid out. How it will perform is another question....
(I can't add comments yet).
If A has A.account_number and C is the table of accounts, why don't you have associations between A and C (and maybe even A and D as well as C and D)?
My "answer" is to add those associations and then just use the proper :include syntax. I don't really see what acts_as_api is providing.
Related
I have 3 models
Options:
class Option < ActiveRecord::Base
attr_accessible :key, :name
belongs_to :item_options
end
ItemOptions
class ItemOption < ActiveRecord::Base
# attr_accessible :title, :body
belongs_to :item
belongs_to :option
end
and Item:
class Item < ActiveRecord::Base
has_many :item_options
has_many :options, :through => :item_options
end
And I need my controller to return all items with their options in JSON format so i'm trying to use .includes but with no luck:
items = Item
.order('id')
.where(manufacturer)
.where('is_active')
render :json => {:data => items.offset(offset), :total => items.count}.to_json(
:include => :options
)
Result does not contain options but i see in console there are appropriate DB requests. However it does only works if i use :include inside to_json:
items = Item
.order('id')
.where(manufacturer)
.where('is_active')
render :json => {:data => items.offset(offset), :total => items.count}.to_json(
:include => :options
)
So the first question is what am i doing wrong so that .include does not work?
But i aslo have problem with working code. I need options to be joined with item_options because options just store options name, options group id and so on, when item_options keeps value for defined option for defined item. And so i'm trying to extend my code as follows:
items = Item
.order('id')
.where(manufacturer)
.where('is_active')
render :json => {:data => items.offset(offset), :total => items.count}.to_json(
:include => {
:options => {
:joins => :item_options
}
}
)
But still, i receive options not beeing joined with item_options. Why?
Also if i use joins inside optoins do i need to define has_many through in items if they loaded without additional information in item_options?
========== UPDATE:
For now i just replaced an options relation to method in Items model:
item = Item
.includes(:item_options)
.find(params[:id])
render :json => item.to_json(:methods => :options)
and in Item model:
has_many :item_options
def options
self.item_options.select('item_options.value, options.name, options.is_feature, options.root').joins('left join options on options.id = item_options.option_id')
end
Don't know, however, if this solution is optimal.
Your calling to_json on a hash and not on the model, and Hash knows nothing about includes or joins.
Try putting your json options on the model
data = items.offset(offset).as_json(include: :options)
render json: {data: data, total: items.count}
Also, I think your Option belongs_to :item_options should be Option has_many :item_options
Using :include within as_json is an instruction to the ActiveModel json serializer telling it to include data from associations in the output JSON.
ActiveModel::Serializers::JSON
(github)
The joins and includes methods are part of ActiveRecord and are used to add join conditions and perform eager loading.
ActiveRecord joins
ActiveRecord includes
I have an polymorphic model called Address. What I'm trying to achieve is to havy many addresses binded to other models (for example User), but still use them like if there was only one, and try to distinguish them by type.
My project was designed to use only one address, so i have a lot pieces of code like User.first.address or User.first.address=... which i can do nothing about.
Here is sort of example:
|id|addressable_type|addressable_id| type | street |
1|User |1 | first | Foo
2|User |1 | second | Bar
3|User |1 | NULL | Other
And what i want to do is:
User.find(1).address(:second)
Which should return <Address street:"Bar">,
User.find(1).address
Which should return <Address street:"Other">,
but User.find(1).addresses should return collection of objects.
I work with a piece of code to add address behaviour to my models:
module Ext::Models::Addressable
def self.included(mod)
super
mod.class_eval do
def self.acts_as_addressable
include InstanceMethods
# Use has_many :delete_all for better performance
has_many :addresses, :as => :addressable, :dependent => :delete_all
has_one :address, :as => :addressable
end
end
end
module InstanceMethods
# I tried with this, but this doesn't even allow me to start rails becauses raises "there is no address method"
def address_with_type(type = nil)
if type
self.addresses.find(:first, :conditions => ["type = ?", type.to_s])
else
address_without_type
end
end
alias_method_chain :address, :type
end
end
I'm using Rails 2.3.14 and Ruby 1.9.2.
If this is possible i also'd like to know what is best way to create new addresses for given model (lets say User).
Thanks in advance.
Solution
I narrowed down all the possibilites, and currently in project there are only two types of addresses. I was able to simplify my approach a lot. Currently my self.included looks like:
has_many :addresses, :as => :addressable, :dependent => :delete_all
has_one :address, :as => :addressable, :conditions => {:type => nil}
has_one :foo_address, :class_name => 'Address', :as => :addressable, :conditions => {:type => "foo"}
And now i'm able to build new object like this:
user.build_address(:street => "abc") and user.build_foo_address(:street => "def"), what is exacly what I need.
I would be a bit careful about overriding default and expected behaviour in that way. Is there a reason you cant let user.address be?
I'm not exactly sure what problems you are solving but maybe you could do something like:
class User < ActiveRecord::Base
has_many :addresses, :as => :addressable, :dependent => :delete_all
def address(only_type = "primary")
addresses.find_first("type" => only_type)
end
def address=(addr)
addresses.delete address
addresses << addr.update_attribute("type", "primary")
end
def build_address(attr)
addresses.delete address
addresses.build attr.merge("type" => "primary")
end
def create_address(attr)
addresses.delete address
addresses.create attr.merge("type" => "primary")
end
end
I haven't tested this code and I'm not that familiar with rails 2 any more so just use it as inspiration. You could wrap it in some meta stuff to modularize it. Btw, if you are using "type" you get STI, not sure if that is what you want.
I'm trying to rewrite a query with tire.
This is the model that I have:
class User < ActiveRecord::Base
has_many :bookmarks, :dependent => :destroy
has_many :followed_venues, :through => :bookmarks, :source => :bookmarkable, :source_type => 'Venue'
end
A user can follow venues. And I need to search for venues that are followed by a certain users.
So far I've been doing it with ActiveRecord:
#user.followed_venues.where(["venues.title LIKE ?", "%"+params[:q]+"%"])
This is obviously not ideal, so I added elasticsearch to my app with tire.
How would I search for venues with tire, filtering by the user that is following them?
I'm going to post an answer to my own question.
So it's quite easy to just search venues. Standard Venue.tire.search {...} The problem is how to filter by user that follows venues. Instead of using the Venue model for searching, I decided to index bookmarks.
This is my bookmark model
class Bookmark < ActiveRecord::Base
belongs_to :bookmarkable, :polymorphic => true
def to_indexed_json
{
:title => bookmarkable.display_name,
:user_id => user_id
}.to_json
end
end
After this I have the user_id and the venue name in the index. Now search becomes as simple as this:
Bookmark.tire.search :load => {:include => 'bookmarkable'}, :page => page, :per_page => per_page do
query do
fuzzy :title => { :value => query.downcase, :min_similarity => 0.6, :prefix_length => 2}
end
filter :terms, {:bookmarkable_type => ["Venue"], :user_id => [user.id]}
end
Now this is not a complete solution. And I hope i'm even using filter :terms correctly. The result that I get back now is an array of bookmarks actually. But it's easy to load the actual venues for them, and maybe wrap it in a WillPaginate collection for better pagination on the frontend.
Any problems with this solution? How would it compare to what phoet suggested with putting user ids to the venue index?
i would create a document for each venue and then add a field with an array of all the user-ids that are following.
are you really sure, that this is a proper task for elasticsearch?
i guess it would be way easier to just search the index for the name of the venue and then look up the data you need in your relational database.
I have 2 models A and B.
class A < ActiveRecord::Base
has_one :b
acts_as_ferret :fields => [:title,:description]
In a_cotroller, i wrote:
#search=A.find_with_ferret(params[:st][:text_search],:limit => :all).paginate :per_page =>10, :page=>params[:page]
The above title and description search is properly working.
class B < ActiveRecord::Base
belongs_to :a
Now,I want to perform a text search by using 3 fields; title, description(part of A) and comment(part of B). Where I want to include the comment field to perform the ferret search.Then,what other changes needed.
The documentation of find_with_ferret indicates that you simply code :store_class_name => :true to enable search over multiple models. While this is true there is a little more to it. To search multiple do the following:
#search = A.find_with_ferret(
params[:st][:text_search],
:limit => :all,
:multi => [B]
).paginate :per_page =>10, :page=>params[:page]
Notice the multi option. This is an array of the additional indexes to search.
Now to get his to work you have to rebuild your indexes after adding :store_class_name => :true to the index definitions.
class A < ActiveRecord::Base
has_one :b
acts_as_ferret :store_class_name => :true, :fields => [:title, :description]
end
OR...
You can simply include Bs fields in the index definition:
class A < ActiveRecord::Base
has_one :b
acts_as_ferret :fields => [:title, :description],
:additional_fields => [:b_content, :b_title]
def b_content
b.content
end
def b_title
b.title
end
end
This makes everything simple, but doesn't allow to search the B model independently of A.
I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])