Separate REST JSON API server and client?
I am looking for advice on how to consume my own API (like Twitter is said to) for an app that I plan on making.
I would like to have a REST API that I can then use for an web app, Android app and some analytics/dashboard apps.
Rails has a respond_with option, and I have seen some apps that have an html and json option, but I think that is a less than stellar way of doing things and json is data and html is for presentation, and you are not making use of your json API essentially
It seems stupid but how exactly would I use my REST api from Rails if I want to do a server side html solution? Using something like HTTParty seems like a lot of work, is there a way to access the API more directly (in ASP MVC for example you can instantiate a controller class and then call its methods for example.)
You can use HTTParty and create client model classes that resemble a rails models by including ActiveModel modules.
activeresource gem was used in previous rails versions, but they deprecated it in favor of HTTParty+ActiveModel like solutions.
Update
I have crafted this example (from memory) with the basic ideas, is not a full implementation but I think you will get the idea.
class Post
# Attributes handling
include Virtus
# ActiveModel
include ActiveModel::Validations
extend ActiveModel::Naming
include ActiveModel::Conversion
# HTTParty
include HTTParty
# Virtus attributes
attribute :id, Integer
attribute :title, String
attribute :content, Text # not sure about this one, read virtus doc
# Validations
validates :title, presence: true
def save
return false unless valid?
if persisted?
self.class.put("/posts/#{id}", attributes)
else
self.class.post("/posts", attributes)
end
end
# This is needed for form_for
def persisted?
# If we have an id we assume this model is saved
id.present?
end
def decorate
#decorator ||= PostDecorator.decorate(self)
end
end
gems needed:
httparty
activemodel (present in rails)
virtus
draper
Related
I have an existing simple rails app that I'm trying to modify.
I'm working on getting an ActiveModel model to work in this app (existing models all use dynamoid). I built the class and was able to get it to successfully populate a show view.
However, everything blows up when I try to switch to an edit view.
I believe the fix for this is to add include ActiveModel::API inside the class.
At least that is what the docs seem to suggest.
However, when I do that, I get
NameError (uninitialized constant ActiveModel::API)
I found the right gem and installed it gem install activemodel and then tried again, but even with the right gem, the same error occurs. I've tried restarting the rails app - no luck. I've tried adding in a require 'active_model' at the top of the file (as suggested in some probably unrelated github issue) - no luck. I dug through the gemfile.lock and found that it already required an older version of activemodel, so I uninstalled the newer version and installed the older version, restarted rails again, but no luck. Searching for the error message results in red herrings - usually dealing with class-referencing issues within an app rather than with referencing a gem.
If it matters here are my versions:
ruby: 2.7.4
activemodel: 6.1.3.1
rails: 6.1.3.1
Here is what the beginning of my class looks like:
# app/models/complex_model.rb
require 'active_model'
class ComplexModel
include ActiveModel::API
My question: How can I get past this uninitialized constant issue?
You're using the docs for the wrong version of Rails.
ActiveModel::API was introduced in Rails 7. And frankly there is not a whole lot going on if you strip the comments out:
module ActiveModel
module API
extend ActiveSupport::Concern
include ActiveModel::AttributeAssignment
include ActiveModel::Validations
include ActiveModel::Conversion
included do
extend ActiveModel::Naming
extend ActiveModel::Translation
end
def initialize(attributes = {})
assign_attributes(attributes) if attributes
super()
end
# Indicates if the model is persisted. Default is +false+.
#
# class Person
# include ActiveModel::API
# attr_accessor :id, :name
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.persisted? # => false
def persisted?
false
end
end
end
What they did is basically shuffle the parts out of the ActiveModel::Model module into yet another layer. ActiveModel::Model consists of parts that where abstracted out of ActiveRecord in Rails 5 and made usable for non-database backed models and as a foundation blocks for building things like Object Relational Managers and even API client data wrappers.
The idea is that ActiveModel::API will contain just the bare necissities needed to interact with stuff like the polymorphic routing and the I18n module while ActiveModel::Model will at some point include ActiveModel::Attributes which is incompatible with ORMs that provide their own attributes implementation such as ActiveRecord.
Whatever you're doing in Rails 6 you can probally solve it simply including ActiveModel::Model and ActiveModel::Attributes.
Is it possible when using the MailView gem or Rails 4.1 mail previews to pass parameters into the MailView? I would love to be able to use query string parameters in the preview URLs and access them in the MailView to dynamically choose which record to show in the preview.
I stumbled upon the same issue and as far as I understand from reading the Rails code it's not possible to access request params from mailer preview.
Crucial is line 22 in Rails::PreviewsController (email is name of the mailer method)
#email = #preview.call(email)
Still a relevant question and still very few solutions to be found on the web (especially elegant ones). I hacked my way through this one today and came up with this solution and blog post on extending ActionMailer.
# config/initializers/mailer_injection.rb
# This allows `request` to be accessed from ActionMailer Previews
# And #request to be accessed from rendered view templates
# Easy to inject any other variables like current_user here as well
module MailerInjection
def inject(hash)
hash.keys.each do |key|
define_method key.to_sym do
eval " ##{key} = hash[key] "
end
end
end
end
class ActionMailer::Preview
extend MailerInjection
end
class ActionMailer::Base
extend MailerInjection
end
class ActionController::Base
before_filter :inject_request
def inject_request
ActionMailer::Preview.inject({ request: request })
ActionMailer::Base.inject({ request: request })
end
end
Since Rails 5.2, mailer previews now have a params attr reader available to use inside your previews.
Injecting requests into your mailers is not ideal as it might lead to thread safety issues and also means your mailers won't work with ActiveJob & co
I have trouble thinking of a way on how to shorten my process on titleizing values upon rendering them in my view.
I did some custom getters for the following attributes that I need to titleize. Here's my example.
user.rb
class User < ActiveRecord::Base
def department
read_attribute(:department).titleize
end
def designation
read_attribute(:designation).titleize
end
end
This method works but it seems a hassle when I want to do this to other models as well.
Is there a more efficient way to handle this which can be used by other models? If you'll mention Draper (since I don't seem to find on how to titleize selected attributes), how can I accomplish using this gem? But, I would prefer not using a gem but instead, create a custom one.
Not tested this, but you could use a Concern with added modules to handle it
--
Modularity
I found a gem called modularity which basically allows you to pass parameters to a concern & other modules. This means if you can pass the params you wish to "titleize", you may be able to pull it off like this:
#Gemfile
gem 'modularity', '~> 2.0.1'
#app/models/concerns/titleize.rb
module Titleize
extend ActiveSupport::Concern
as_trait do |*fields|
fields.each do |field|
define_method("#{field}") do
self[field.to_sym] = field.titleize
end
end
end
end
#app/models/your_model.rb
Class YourModel < ActiveRecord::Base
include Titleize[:your, :params]
end
If you want those value always titleized, what you are doing is fine, but I would actually apply the method on the setters, not on the getters, so you only do it once per record instead of at each read:
def department=(s)
write_attribute(:department, s.to_s.titleize) # The to_s is in case you get nil/non-string
end
If this is purely for presentation (ie, you want the not titleized version in the database, then it can be done in a presenter using Draper:
class UserDecorator < Draper::Decorator
delegate_all
def designation
object.designation.titleize
end
end
(or another rails presenter).
I am developing a Rails 3 app in a largely tabless capacity. I am using savon_model and ActiveModel to generate similar behaviour to ActiveRecord equivalents. Below is my code:
class TestClass
include Savon::Model
include ActiveModel::Validations
# Configuration
endpoint "http://localhost:8080/app/TestService"
namespace "http://wsns.test.com/"
actions :getObjectById, :getAllObjects
attr_accessor :id, :name
def initialize(hash)
#id = hash[:id]
#name = hash[:name]
end
client do
http.headers["Pragma"] = "no-cache"
end
def self.all
h = getAllObjects(nil).to_array
return convert_array_hash_to_obj(h, :get_all_objects_response)
end
def self.find(id)
h = getObjectById(:arg0 => id).to_hash
return convert_hash_to_obj(h, :get_object_by_id_response)
end
private
def self.convert_array_hash_to_obj(arrayhash, returnlabel)
results = Array.new
arrayhash.each do |hash|
results << convert_hash_to_obj(hash, returnlabel)
end
return results
end
def self.convert_hash_to_obj(hash, returnlabel)
return TestClass.new(hash[returnlabel][:return])
end
end
OK, so everything works as expected; values are pulled from the web service and onto the page. Unfortunately, when I look at the html produced at the client side there are some issues. The Show links are along the following lines:
/testclasses/%23%3CTestClass:0xa814cb4%3E
instead of...
/testclasses/1
So, I did a print of the object (hash?) to the console to compare the outputs.
[#<System:0xa814cb4 #id="1", #name="CIS">]
instead of what I believe it should be...
[#<System id="1", name="CIS">]
I have three questions:
1: What is the hex suffix on my class name when it is printed out
2: How can I modify my class to match the desired output when printed to the console?
3: Why are the frontend links (Show, Edit, Delete) broken and is there an easy fix?
Thanks so much for your time and apologies for rubbish code / stupid questions. This is my first Ruby or Rails app!
Gareth
The hex suffix is the object id of your instance of System
You can manipulate the output on the console by implementing an inspect instance method
The Rails url helpers use the to_param instance method to build these links. You should implement this if you are going to use your class as an ActiveRecord substitute.
Generally speaking, if you want to use all the Rails goodies with an own implementation of a model class, you should use ActiveModel:Lint::Test to verify which parts of the ActiveModel APIs are working as expected.
More information can be found here: http://api.rubyonrails.org/classes/ActiveModel/Lint/Tests.html
I want to use REST in an Rails application that uses inherited_resources,
but I want certain properties not to be revealed during json and xml request.
Let's call that field 'password'.
I know I can overwrite the to_xml and to_json methods and then
super :except => [:password]
But I would have to do that for to_xml and to_json. Not very DRY.
Any ideas?
rest_member_defaults :except => [:password]
in the controller is vaguely what I'm aiming for.
Thanks!
I had this exact question and yours triggered me to wrap this up into a plugin hide_attributes which is also available as a gem.
Just add it to your Gemfile:
gem 'hide_attributes'
And then add something like this to your model:
class User < ActiveRecord::Base
hide_attributes :password, :password_salt
end
And there you go. Sorry that there are no tests yet and documentation is rather thin.