Saving nested JSON object to database - ruby

Lets say I have a model.
Passengers belongs to Flights.
Flights belongs to Trips
class Trip < ActiveRecord::Base
has_many :flights, :dependent => :destroy, :order => "order_num ASC"
end
class Flight < ActiveRecord::Base
belongs_to :trip, touch: true
has_many :passengers, :dependent => :destroy
end
class Passenger < ActiveRecord::Base
belongs_to :flight, touch: true
end
And I'm getting this sent back to the rails app. (when the user calls save).
*The top level is the trip
{
name: 'Hello Trip',
date: '2013-08-12',
flights: [
{
id: 1
depart_airport: 'RDU',
arrive_airport: 'RDU',
passengers: [
{
user_id: 1,
request: true
}
]
},
{
depart_airport: 'RDU',
arrive_airport: 'RDU',
passengers: [
{
user_id: 1,
request: true
},
{
user_id: 2
request:true
}
]
}
]
}
Right now I'm getting the saved json in and manually looping through the flights to see if there is an id. If there is i'm updating it. If not I'm creating a new one. Then adding the passengers.
I'm wondering if there is an automatic format that Rails takes that can do all the saving for me. I know when you submit a nested form it creates a similar pattern, and adds a _destroy property and the id is a timestamp if it's just created. Would the JSON saving be similar to that?
Thanks for any help!

Yes, you should be able to use accepts_nested_attributes_for here.
You'll need to enable accepts_nested_attributes_for on your models, e.g.,
class Trip < ActiveRecord::Base
has_many :flights, :dependent => :destroy, :order => "order_num ASC"
accepts_nested_attributes_for :flights, :allow_destroy => true
attr_accessible :flights_attributes
end
You'll also need to ensure that your JSON response uses keys that Rails will recognize. You can either modify the JSON response or do something like:
response = JSON.parse(json_string)
response[:flights_attributes] = response.delete(:flights)
# ...
Then you can just do
Trip.create(response)
You'll want to ensure that everything is created/updated as expected. For more on accepts_nested_attributes_for, see the documentation: http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for.
I think accepts_nested_attributes_for is convenient, but note that there are some that think it should be deprecated (e.g., here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/, see here for a response: https://stackoverflow.com/a/17639029/1614607).

Related

Get a belongs_to relationship from class Ruby on Rails

Here are my models:
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Order < ApplicationRecord
has_many :order_items
end
Here is my controller:
def index
orders = Order.where(nil)
render json: {'orders': orders}, include:
['order_items'], status: :ok
end
I want to also include the product in the order_items. How can I achieve this to get the following JSON:
{
"id": 2,
"order_items": [
{
"id": 1,
"product": {
"name": "abc"
},
}
]
},
You can reach this with changing
include: ['order_items']
to
include: ['order_items', 'order_items.product'].
More details you can get here.
I have been able to solve this by changing include: ['order_items'] to include: {'order_items': {include: 'product'}}

Rails 5 belongs_to scoped validation

lets say I have this model:
class Post < ApplicationRecord
enum post_type: { post: 0, page: 1 }
belongs_to :user
end
by default rails 5 will make the belongs_to :user association to be required. And If you pass optional: true will make this association to be optional. But what I want is the belongs_to :user association to be optional only when the post_type is page and when it is post to required.
How can I do it at the line belongs_to :user ?
At this moment I am doing this:
class Post < ApplicationRecord
enum post_type: { post: 0, page: 1 }
belongs_to :user, optional: true
validates :user_id, presence: { scope: post? }
end
But this will give me an error like:
NoMethodError: undefined method `post?' for #
Is this the correct way to do it? or there is another way?
The user presence can be validated using if option:
validates :user, presence: true, if: :post?

Neo4j and Ruby/Rails: How to only return nodes based on user permissions

How to return an index of only Items where the User has permission?
How to return an index of only Items where the Group a User is in has permission?
How to return a single item only if a User has permission?
How to return a single item only if the Group a User is in has permission?
Note: I know it's possible to query for Item.all, then iterate through the array and pull out only items where .has_permissions == User, but this completely ignores the benefits of having everything in a graph, so is not an answer.
To keep this simple, let's say there are 3 objects:
An Item
A User
A Group
Typical graph situations:
(User)<-[:HAS_PERMISSIONS]-(Item)
(Group)<-[:HAS_PERMISSIONS]-(Item)
(Group)-[:HAS_MEMBERS]->(User)
With the models:
class Item
include Neo4j::ActiveNode
property :name, type: String
property :description, type: String
has_many :out, :user_permission_to, type: :PERMISSION_TO, model_class: :User
has_many :out, :group_permission_to, type: :PERMISSION_TO, model_class: :Group
end
class Identity
include Neo4j::ActiveNode
property :username, type: String
has_many :in, :permission_to, type: :PERMISSION_TO, model_class: :Item
has_many :in, :groups, type: :GROUP_MEMBER, model_class: :Group
end
class Group
include Neo4j::ActiveNode
property :group_name, type: String
has_many :in, :permission_to, type: :PERMISSION_TO, model_class: :Item
has_many :out, :members, type: :GROUP_MEMBER, model_class: :User
end
And with the simple controller:
# GET /items
def index
#items = Item.all
render json: #items
end
# GET /item/1
def show
render json: #item
end
For starters, I'd suggest checking out this article (the second half covers access control which is very similar)
"How to return an index of only Items where the User has permission?"
You could do this a couple of ways. More explicitly:
identity.as(:id).query.match("(id)<-[PERMISSION_TO*1..2]-(item:Item)").pluck(:item)
Alternatively, I think that this would work:
identity.permission_to(rel_length: 1..2)
"How to return an index of only Items where the Group a User is in has permission?"
Simple:
identity.groups.permission_to
"How to return a single item only if a User has permission?"
For the two solutions above:
identity.as(:id).query.match("(id)<-[PERMISSION_TO*1..2]-(item:Item)").limit(1).pluck(:item)
# or
identity.permission_to(rel_length: 1..2).first
"How to return a single item only if the Group a User is in has permission?"
identity.groups.permission_to
Separately, some feedback:
Using the term "index" the way you're using it is a bit confusing because Neo4j has indexes which allow for performant querying of properties on labels.
I would probably make my models like this:
class Item
include Neo4j::ActiveNode
property :name, type: String
property :description, type: String
has_many :in, :users_with_permission, type: :CAN_ACCESS, model_class: :Identity
has_many :in, :groups_with_permission, type: :CAN_ACCESS, model_class: :Group
end
class Identity
include Neo4j::ActiveNode
property :username, type: String
has_many :out, :accessible_items, type: :CAN_ACCESS, model_class: :Item
has_many :out, :groups, type: :IN_GROUP # Don't need `model_class: :Group` here
end
class Group
include Neo4j::ActiveNode
property :group_name, type: String
has_many :out, :accessible_items, type: :CAN_ACCESS, model_class: :Item
has_many :in, :members, type: :IN_GROUP, model_class: :Identity
# You could also do:
# has_many :in, :members, model_class: :Identity, origin: :groups
end

How can I combine two Rails 4 where queries that query either side of a boolean condition?

Question:
Is there a way to combine the following two queries (including the assignments) into one query? I'm not sure how much time I'd really save. In other words, I'm not sure if it is worth it, but I'd like to be as efficient as possible.
#contacts = #something.user.contacts.where.not(other_user_id: 0)
#user_contacts = #something.user.contacts.where(other_user_id: 0)
More context:
Here is my contacts table from schema.rb:
create_table "contacts", force: true do |t|
t.string "name"
t.string "email"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "profile_picture"
t.string "phone_number"
t.integer "other_user_id"
end
And here is the important stuff from the users table:
create_table "users", force: true do |t|
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
...
t.string "first_name"
t.string "second_name"
end
And here is the pertinent information of the models:
class Contact < ActiveRecord::Base
belongs_to :user
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :name, presence: true
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
validates :user_id, presence: true
def get_email_from_name
self.email
end
end
[...]
class User < ActiveRecord::Base
has_many :contacts
has_many :relationships,
foreign_key: 'follower_id',
dependent: :destroy
has_many :reverse_relationships,
foreign_key: 'followed_id',
class_name: 'Relationship',
dependent: :destroy
has_many :commitments,
class_name: 'Commitment',
dependent: :destroy
has_many :followers,
through: :reverse_relationships
has_many :followed_users,
through: :relationships,
source: :followed
[...]
before_save { email.downcase! || email }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email,
presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
[...]
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
if create_contact?(self, id, other_user.id)
create_contact(other_user.name,
other_user.email,
self.id,
other_user.id,
other_user.gravatar_url)
elsif create_contact?(other_user, other_user.id, id)
create_contact(name, email, other_user.id, id, gravatar_url)
end
end
def create_contact?(user, followed_id, follower_id)
user.admin? && ! Relationship.where(followed_id: followed_id, follower_id: follower_id).empty?
end
def create_contact(name, email, user_id, other_user_id, profile_picture)
Contact.create!(name: name,
email: email,
user_id: user_id,
other_user_id: other_user_id,
profile_picture: profile_picture)
end
def unfollow!(other_user)
relationships.find_by(followed_id: other_user.id).destroy
Contact.destroy_all(user_id: self.id, other_user_id: other_user.id)
end
[...]
end
The other contacts that may not have an account with the website (yet), and I'd like to distinguish that in the view. So I keep track of which contacts I import through Google contacts using the omniauth gem. For the other contacts, I gather the other users that are friends with current_user.
Goal:
I'd like to save these two record collections to use in the view, but I'd like to avoid looking through all the user's contacts twice, checking the same column in each pass-through.
Any ideas? I'm sure there are lots of ways this can be done, and I'd like to learn as much as I can from this! Thanks in advance!
You can use Array#partition to split up the array in memory, after the query was performed.
#user_contacts, #contacts = #something.user.contacts.partition{|u| other.id == 0 }
However checking for this magic 0 id is smelly. You should try to get rid of such special cases whenever possible.
It is not the best solution, but if you think partition hard to understand, it can be an optional.
#user_contacts, #users = [], []
#something.user.contacts.each do |record|
if record.other_user_id == 0
#user_contacts << record
else
#users << record
end
end

FactoryGirl ActiveRecord::RecordInvalid: Validation failed: Name has already been taken

I have three models, Course, Category and partner, a course can have many categories and a course belongs to one partner. When i create my course factory i get the following error:
Partner has a valid factory for course
Failure/Error: expect(FactoryGirl.create(:course)).to be_valid
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
Here are my models:
class Category < ActiveRecord::Base
has_many :categorisations
has_many :courses, :through=> :categorisations
belongs_to :user
#validation
validates :name, presence: true , uniqueness: { scope: :name }
end
class Partner < ActiveRecord::Base
has_many :courses
belongs_to :user
validates :name, presence: true, uniqueness: { scope: :name }
validates :short_name, presence: true
VALID_HEX_COLOR= /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/
validates :primary_color, presence: true, format: { with: VALID_HEX_COLOR}
validates :secondary_color, presence: true, format: { with: VALID_HEX_COLOR}
end
class Course < ActiveRecord::Base
extend FriendlyId
friendly_id :title, use: [:slugged, :history]
has_many :categorisations, :dependent => :destroy
has_many :categories, :through=> :categorisations
belongs_to :partner
belongs_to :user
# validates_uniqueness_of :title
validates :title, presence: true
# validates :start_date, presence: true
# validates :duration, presence:true
# validates :state, presence:true
validates :categories, length: { minimum: 1 , message:"please select"}
validates :partner_id, presence: true, allow_nil: false
end
Here are my factories:
factory :partner do |f|
f.name Faker::Name.name
f.short_name "UCT"
f.primary_color "#009bda"
f.secondary_color "#002060"
end
factory :course do |f|
f.title "Introduction to Accounting short course"
f.start_date "2014-02-27 00:00:00"
f.duration "10 WEEKS ONLINE"
partner
categorisation
end
factory :categorisation do |categorisation|
categorisation.category {|category| category.association(:category)}
categorisation.course {|course| course.association(:course)}
end
I am not to sure what i am doing wrong, if anyone could advise me on what the problem may be or the process i can go about fixing this problem may be that would be a great help
try this out:
factory :partner do |f|
f.sequence(:name) { |n| "#{Faker::Name.name} #{n}" }
f.short_name "UCT"
f.primary_color "#009bda"
f.secondary_color "#002060"
end
factory :category do |f|
f.sequence(:name) { |n| "Category #{n}" }
end
All that i had to do was to add the following line to my course factory:
categories {[FactoryGirl.create(:category)]}
couse factory:
factory :course do |f|
f.title "Introduction to Accounting short course"
f.start_date "2014-02-27 00:00:00"
f.duration "10 WEEKS ONLINE"
partner
categories {[FactoryGirl.create(:category)]}
end

Resources